import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { z } from 'zod';
import { requireAdminRole } from '../middleware/auth';
import { videoFetchQueueService } from '../../../services/video-fetch-queue.service';
import { logger } from '../../../utils/logger';

// Validation schema for fetch submission
const FetchBodySchema = z.object({
  urls: z.array(z.string().url()).min(1, 'At least one URL is required').max(20, 'Maximum 20 URLs per submission'),
});

export async function fetchRoutes(fastify: FastifyInstance) {
  /**
   * POST /fetch — Submit a new fetch job
   */
  fastify.post(
    '/fetch',
    { preHandler: requireAdminRole },
    async (request: FastifyRequest, reply: FastifyReply) => {
      try {
        const body = FetchBodySchema.parse(request.body);

        const result = await videoFetchQueueService.submitFetch(
          body.urls,
          request.user!.id
        );

        return reply.code(201).send({
          message: `Fetch job submitted with ${result.urlCount} URL(s)`,
          jobId: result.jobId,
          urlCount: result.urlCount,
          urls: result.sanitizedUrls,
        });
      } catch (err) {
        if (err instanceof z.ZodError) {
          return reply.code(400).send({
            message: 'Invalid request',
            errors: err.errors.map(e => e.message),
          });
        }

        const message = err instanceof Error ? err.message : 'Failed to submit fetch job';
        const statusCode = message.includes('No valid URLs') || message.includes('Maximum 20') ? 400 : 500;
        return reply.code(statusCode).send({ message });
      }
    }
  );

  /**
   * GET /fetch/jobs — List recent fetch jobs
   */
  fastify.get(
    '/fetch/jobs',
    { preHandler: requireAdminRole },
    async (request: FastifyRequest, reply: FastifyReply) => {
      try {
        const query = request.query as { limit?: string };
        const limit = Math.min(parseInt(query.limit || '20'), 50);
        const jobs = await videoFetchQueueService.getRecentJobs(limit);
        return reply.send({ jobs });
      } catch (err) {
        logger.error('Failed to list fetch jobs', { error: err });
        return reply.code(500).send({ message: 'Failed to list fetch jobs' });
      }
    }
  );

  /**
   * GET /fetch/jobs/:jobId — Get single job detail
   */
  fastify.get(
    '/fetch/jobs/:jobId',
    { preHandler: requireAdminRole },
    async (request: FastifyRequest, reply: FastifyReply) => {
      try {
        const { jobId } = request.params as { jobId: string };
        const job = await videoFetchQueueService.getJob(jobId);

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

        // Include log lines
        const log = await videoFetchQueueService.getJobLog(jobId);

        return reply.send({ ...job, log });
      } catch (err) {
        logger.error('Failed to get fetch job', { error: err });
        return reply.code(500).send({ message: 'Failed to get fetch job' });
      }
    }
  );

  /**
   * GET /fetch/jobs/:jobId/log — SSE stream of job log lines
   */
  fastify.get(
    '/fetch/jobs/:jobId/log',
    { preHandler: requireAdminRole },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const { jobId } = request.params as { jobId: string };

      const job = await videoFetchQueueService.getJob(jobId);
      if (!job) {
        return reply.code(404).send({ message: 'Job not found' });
      }

      // Set up SSE headers using raw response
      reply.raw.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'X-Accel-Buffering': 'no', // Disable nginx buffering
      });

      // Send existing log lines
      const existingLog = await videoFetchQueueService.getJobLog(jobId);
      for (const line of existingLog) {
        reply.raw.write(`data: ${JSON.stringify({ type: 'log', data: line })}\n\n`);
      }

      // Send current progress
      if (typeof job.progress === 'number' && job.progress > 0) {
        reply.raw.write(`event: progress\ndata: ${job.progress}\n\n`);
      }

      // If job is already done, send final status and close
      if (job.state === 'completed' || job.state === 'failed') {
        reply.raw.write(`data: ${JSON.stringify({ type: 'status', status: job.state })}\n\n`);
        reply.raw.write('event: done\ndata: done\n\n');
        reply.raw.end();
        return;
      }

      // Subscribe to real-time log updates via Redis pub/sub
      const subscriber = videoFetchQueueService.createLogSubscriber(jobId);

      const cleanup = () => {
        try {
          subscriber.unsubscribe();
          subscriber.quit();
        } catch {}
      };

      subscriber.on('message', (_channel: string, message: string) => {
        try {
          if (message === '__DONE__') {
            reply.raw.write('event: done\ndata: done\n\n');
            cleanup();
            reply.raw.end();
          } else if (message.startsWith('__PROGRESS__:')) {
            const percent = message.slice('__PROGRESS__:'.length);
            reply.raw.write(`event: progress\ndata: ${percent}\n\n`);
          } else {
            reply.raw.write(`data: ${JSON.stringify({ type: 'log', data: message })}\n\n`);
          }
        } catch {
          cleanup();
        }
      });

      // Clean up on disconnect
      request.raw.on('close', cleanup);
      request.raw.on('error', cleanup);

      // Safety timeout: close after 2 hours
      const timeout = setTimeout(() => {
        cleanup();
        try { reply.raw.end(); } catch {}
      }, 2 * 60 * 60 * 1000);

      request.raw.on('close', () => clearTimeout(timeout));
    }
  );

  /**
   * DELETE /fetch/jobs/:jobId — Cancel a running/waiting job
   */
  fastify.delete(
    '/fetch/jobs/:jobId',
    { preHandler: requireAdminRole },
    async (request: FastifyRequest, reply: FastifyReply) => {
      try {
        const { jobId } = request.params as { jobId: string };
        const cancelled = await videoFetchQueueService.cancelJob(jobId);

        if (!cancelled) {
          return reply.code(400).send({ message: 'Job cannot be cancelled (may be already completed or not found)' });
        }

        return reply.send({ message: 'Job cancelled', jobId });
      } catch (err) {
        logger.error('Failed to cancel fetch job', { error: err });
        return reply.code(500).send({ message: 'Failed to cancel fetch job' });
      }
    }
  );
}
