import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { createReadStream, stat } from 'fs';
import { access, readFile } from 'fs/promises';
import { join, resolve } from 'path';
import { lookup } from 'mime-types';
import jwt from 'jsonwebtoken';
import { UserRole, UserStatus } from '@prisma/client';
import { prisma } from '../../../config/database';
import { env } from '../../../config/env';
import { logger } from '../../../utils/logger';
import { hasAnyRole, MEDIA_ROLES } from '../../../utils/roles';

/**
 * Check if the request is from an authenticated admin user.
 * Supports JWT from Authorization header or ?token= query parameter
 * (needed for <video src> and <img src> which can't send headers).
 */
async function isAdminRequest(request: FastifyRequest): Promise<boolean> {
  try {
    // Extract token from Authorization header (priority) or query param (fallback)
    let token: string | undefined;
    const authHeader = request.headers.authorization;
    if (authHeader?.startsWith('Bearer ')) {
      token = authHeader.substring(7);
    } else {
      const query = request.query as Record<string, string | undefined>;
      token = query.token;
    }

    if (!token) return false;

    // Verify JWT signature
    const payload = jwt.verify(token, env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] }) as {
      id: string;
      role: UserRole;
      roles?: UserRole[];
    };

    // Check admin role from token (multi-role aware)
    if (!hasAnyRole(payload, MEDIA_ROLES)) return false;

    // Verify user is still active in DB
    const user = await prisma.user.findUnique({
      where: { id: payload.id },
      select: { status: true },
    });

    return user?.status === UserStatus.ACTIVE;
  } catch {
    return false;
  }
}

/**
 * Parse range header for video seeking
 * Example: "bytes=0-1024" or "bytes=1024-"
 */
function parseRange(range: string, fileSize: number): { start: number; end: number } | null {
  const parts = range.replace(/bytes=/, '').split('-');
  const start = parseInt(parts[0], 10);
  const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;

  if (isNaN(start) || isNaN(end) || start > end || end >= fileSize) {
    return null;
  }

  return { start, end };
}

/**
 * Get video file stats safely
 */
async function getFileStats(filePath: string): Promise<{ size: number } | null> {
  return new Promise((resolve) => {
    stat(filePath, (err, stats) => {
      if (err) {
        resolve(null);
      } else {
        resolve({ size: stats.size });
      }
    });
  });
}

export async function videoStreamingRoutes(fastify: FastifyInstance) {
  /**
   * Stream video file with HTTP range support for seeking
   * GET /api/videos/:id/stream
   * Public endpoint (no auth) - videos are public by default
   */
  fastify.get(
    '/:id/stream',
    async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => {
      try {
        const videoId = parseInt(request.params.id);

        if (isNaN(videoId)) {
          return reply.code(400).send({ message: 'Invalid video ID' });
        }

        // Admin bypass: skip publication filter for authenticated admin users
        const admin = await isAdminRequest(request);
        const video = await prisma.video.findFirst({
          where: admin
            ? { id: videoId }
            : { id: videoId, isPublished: true, isLocked: false },
        });

        if (!video) {
          return reply.code(404).send({ message: 'Video not found or not published' });
        }

        // Security: Validate path is within allowed media directory
        const MEDIA_BASE = '/media/local';
        const candidatePath = video.path.endsWith(video.filename)
          ? video.path
          : join(video.path, video.filename);
        const filePath = resolve(candidatePath);
        if (!filePath.startsWith(resolve(MEDIA_BASE))) {
          logger.warn(`Path traversal attempt detected: ${filePath}`);
          return reply.code(403).send({ message: 'Access denied' });
        }

        // Check file exists
        try {
          await access(filePath);
        } catch {
          logger.error(`Video file not found on disk: ${filePath}`);
          return reply.code(404).send({ message: 'Video file not found' });
        }

        // Get file stats
        const stats = await getFileStats(filePath);
        if (!stats) {
          return reply.code(500).send({ message: 'Failed to read video file' });
        }

        const fileSize = stats.size;

        // Determine MIME type
        const mimeType = lookup(video.filename) || 'video/mp4';

        // Handle range requests for seeking
        const rangeHeader = request.headers.range;

        if (rangeHeader) {
          const range = parseRange(rangeHeader, fileSize);

          if (!range) {
            return reply.code(416).send({ message: 'Invalid range' });
          }

          const { start, end } = range;
          const contentLength = end - start + 1;

          // Set partial content headers
          reply.code(206);
          reply.header('Content-Range', `bytes ${start}-${end}/${fileSize}`);
          reply.header('Accept-Ranges', 'bytes');
          reply.header('Content-Length', contentLength);
          reply.header('Content-Type', mimeType);
          reply.header('Cache-Control', 'public, max-age=31536000'); // 1 year

          // Stream the requested range
          const stream = createReadStream(filePath, { start, end });
          return reply.send(stream);
        } else {
          // No range header - stream entire file
          reply.header('Content-Length', fileSize);
          reply.header('Content-Type', mimeType);
          reply.header('Accept-Ranges', 'bytes');
          reply.header('Cache-Control', 'public, max-age=31536000'); // 1 year

          const stream = createReadStream(filePath);
          return reply.send(stream);
        }
      } catch (error) {
        logger.error('Video streaming error:', error);
        return reply.code(500).send({
          message: error instanceof Error ? error.message : 'Failed to stream video',
        });
      }
    }
  );

  /**
   * Serve video thumbnail
   * GET /api/videos/:id/thumbnail
   * Public endpoint - returns thumbnail image or 404
   */
  fastify.get(
    '/:id/thumbnail',
    async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => {
      try {
        const videoId = parseInt(request.params.id);

        if (isNaN(videoId)) {
          return reply.code(400).send({ message: 'Invalid video ID' });
        }

        // Admin bypass: skip publication filter for authenticated admin users
        const admin = await isAdminRequest(request);
        const video = await prisma.video.findFirst({
          where: admin
            ? { id: videoId }
            : { id: videoId, isPublished: true, isLocked: false },
        });

        if (!video) {
          return reply.code(404).send({ message: 'Video not found or not published' });
        }

        // Check if thumbnail exists
        if (!video.thumbnailPath) {
          return reply.code(404).send({ message: 'Thumbnail not found' });
        }

        // Security: Validate path is within allowed media directory
        const resolvedThumb = resolve(video.thumbnailPath);
        if (!resolvedThumb.startsWith(resolve('/media/local'))) {
          logger.warn(`Path traversal attempt detected: ${resolvedThumb}`);
          return reply.code(403).send({ message: 'Access denied' });
        }

        // Check file exists
        try {
          await access(resolvedThumb);
        } catch {
          logger.error(`Thumbnail file not found on disk: ${resolvedThumb}`);
          return reply.code(404).send({ message: 'Thumbnail file not found' });
        }

        // Determine MIME type
        const mimeType = lookup(resolvedThumb) || 'image/jpeg';

        // Read and send thumbnail
        const thumbnailBuffer = await readFile(video.thumbnailPath);

        reply.header('Content-Type', mimeType);
        reply.header('Content-Length', thumbnailBuffer.length);
        reply.header('Cache-Control', 'public, max-age=31536000'); // 1 year

        return reply.send(thumbnailBuffer);
      } catch (error) {
        logger.error('Thumbnail serving error:', error);
        return reply.code(500).send({
          message: error instanceof Error ? error.message : 'Failed to serve thumbnail',
        });
      }
    }
  );

  /**
   * Get public video metadata for embedding
   * GET /api/videos/:id/metadata
   * Public endpoint - returns essential metadata for video players
   */
  fastify.get(
    '/:id/metadata',
    async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => {
      try {
        const videoId = parseInt(request.params.id);

        if (isNaN(videoId)) {
          return reply.code(400).send({ message: 'Invalid video ID' });
        }

        // Admin bypass: skip publication filter for authenticated admin users
        const admin = await isAdminRequest(request);
        const video = await prisma.video.findFirst({
          where: admin
            ? { id: videoId }
            : { id: videoId, isPublished: true, isLocked: false },
        });

        if (!video) {
          return reply.code(404).send({ message: 'Video not found or not published' });
        }

        // Construct public URLs (use relative paths for nginx proxy routing)
        const streamUrl = `/media/videos/${video.id}/stream`;
        const thumbnailUrl = video.thumbnailPath
          ? `/media/videos/${video.id}/thumbnail`
          : null;

        // Return public metadata
        return {
          id: video.id,
          title: video.title || video.filename,
          durationSeconds: video.durationSeconds,
          width: video.width,
          height: video.height,
          orientation: video.orientation,
          hasAudio: video.hasAudio,
          quality: video.quality,
          streamUrl,
          thumbnailUrl,
          createdAt: video.createdAt,
        };
      } catch (error) {
        logger.error('Metadata retrieval error:', error);
        return reply.code(500).send({
          message: error instanceof Error ? error.message : 'Failed to retrieve metadata',
        });
      }
    }
  );

  /**
   * Health check for streaming routes
   */
  fastify.get('/stream/health', async () => {
    return {
      status: 'ok',
      service: 'video-streaming',
    };
  });
}
