import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { createReadStream } from 'fs';
import { access, unlink } from 'fs/promises';
import { resolve } from 'path';
import { Prisma } from '@prisma/client';
import { prisma } from '../../../config/database';
import { requireAdminRole } from '../middleware/auth';
import { logger } from '../../../utils/logger';

const DOCUMENTS_BASE = '/media/local/documents';

interface PublicListQuery {
  tag?: string;
  category?: string;
  published?: string;
  limit?: string;
  offset?: string;
}

interface DocumentUpdateBody {
  title?: string;
  description?: string;
  tags?: string[];
  category?: string;
  isPublished?: boolean;
  position?: number;
}

function serializeDocument<T extends { fileSize: bigint | null }>(doc: T) {
  return {
    ...doc,
    fileSize: doc.fileSize?.toString() ?? null,
  };
}

export async function documentsRoutes(fastify: FastifyInstance) {
  fastify.get<{ Querystring: PublicListQuery }>(
    '/',
    async (request) => {
      const limit = Math.min(parseInt(request.query.limit || '100'), 500);
      const offset = parseInt(request.query.offset || '0');
      const { tag, category } = request.query;
      const publishedParam = request.query.published;

      const where: Prisma.DocumentWhereInput = {};

      if (publishedParam === undefined || publishedParam === 'true') {
        where.isPublished = true;
      } else if (publishedParam === 'false') {
        where.isPublished = false;
      }

      if (category) where.category = category;
      if (tag) {
        where.tags = { array_contains: tag } as Prisma.JsonFilter;
      }

      const items = await prisma.document.findMany({
        where,
        orderBy: [
          { position: 'asc' },
          { createdAt: 'desc' },
        ],
        take: limit,
        skip: offset,
      });

      return { items: items.map(serializeDocument) };
    }
  );

  fastify.get<{ Params: { id: string } }>(
    '/:id',
    async (request, reply) => {
      const { id } = request.params;
      const document = await prisma.document.findUnique({ where: { id } });

      if (!document) {
        return reply.code(404).send({ message: 'Document not found' });
      }

      if (!document.isPublished) {
        return reply.code(404).send({ message: 'Document not found' });
      }

      return serializeDocument(document);
    }
  );

  fastify.get<{ Params: { id: string } }>(
    '/:id/download',
    async (request, reply) => {
      const { id } = request.params;

      const document = await prisma.document.findUnique({ where: { id } });
      if (!document || !document.isPublished) {
        return reply.code(404).send({ message: 'Document not found' });
      }

      const resolvedPath = resolve(document.path);
      if (!resolvedPath.startsWith(resolve(DOCUMENTS_BASE) + '/')) {
        logger.warn(`Document path traversal attempt blocked: ${document.path}`);
        return reply.code(403).send({ message: 'Access denied' });
      }

      try {
        await access(resolvedPath);
      } catch {
        return reply.code(404).send({ message: 'Document file not found' });
      }

      await prisma.document.update({
        where: { id },
        data: { downloadCount: { increment: 1 } },
      });

      const downloadName = document.originalFilename || document.filename;
      const safeName = downloadName.replace(/"/g, '');

      reply.header('Content-Type', document.mimeType);
      reply.header('Content-Disposition', `attachment; filename="${safeName}"`);
      if (document.fileSize) {
        reply.header('Content-Length', document.fileSize.toString());
      }
      return reply.send(createReadStream(resolvedPath));
    }
  );

  fastify.put<{ Params: { id: string }; Body: DocumentUpdateBody }>(
    '/:id',
    { preHandler: requireAdminRole },
    async (request: FastifyRequest<{ Params: { id: string }; Body: DocumentUpdateBody }>, reply: FastifyReply) => {
      const { id } = request.params;
      const { title, description, tags, category, isPublished, position } = request.body;

      const existing = await prisma.document.findUnique({ where: { id } });
      if (!existing) {
        return reply.code(404).send({ message: 'Document not found' });
      }

      const updated = await prisma.document.update({
        where: { id },
        data: {
          ...(title !== undefined && { title }),
          ...(description !== undefined && { description }),
          ...(category !== undefined && { category }),
          ...(isPublished !== undefined && { isPublished }),
          ...(position !== undefined && { position }),
          ...(tags !== undefined && {
            tags: tags === null
              ? Prisma.JsonNull
              : (tags as unknown as Prisma.InputJsonValue),
          }),
        },
      });

      return serializeDocument(updated);
    }
  );

  fastify.delete<{ Params: { id: string } }>(
    '/:id',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const { id } = request.params;

      const document = await prisma.document.findUnique({ where: { id } });
      if (!document) {
        return reply.code(404).send({ message: 'Document not found' });
      }

      const resolvedPath = resolve(document.path);
      if (resolvedPath.startsWith(resolve(DOCUMENTS_BASE) + '/')) {
        try {
          await unlink(resolvedPath);
        } catch (err) {
          logger.warn(`Failed to unlink document file ${resolvedPath}:`, err);
        }
      }

      await prisma.document.delete({ where: { id } });

      return { message: 'Document deleted' };
    }
  );
}
