import { FastifyInstance } from 'fastify';
import { createReadStream, stat } from 'fs';
import { access } from 'fs/promises';
import { join, resolve } from 'path';
import { lookup } from 'mime-types';
import { prisma } from '../../../config/database';
import { optionalAuth } from '../middleware/auth';
import { logger } from '../../../utils/logger';

/**
 * Public routes for media gallery
 * No authentication required, only shows published videos
 */

interface PublicVideosQuery {
  limit?: string;
  offset?: string;
  search?: string;
  sort?: 'recent' | 'popular' | 'oldest';
  category?: 'videos' | 'curated' | 'compilations' | 'playback' | 'highlights';
}

export async function publicRoutes(fastify: FastifyInstance) {
  // GET /api/public - List published videos (unauthenticated)
  fastify.get<{ Querystring: PublicVideosQuery }>(
    '/public',
    {
      preHandler: optionalAuth,
    },
    async (request, reply) => {
      const limit = parseInt(request.query.limit || '24');
      const offset = parseInt(request.query.offset || '0');
      const search = request.query.search;
      const sort = request.query.sort || 'recent';
      const category = request.query.category;

      // Build Prisma WHERE clause - only published videos
      const where: any = {
        isPublished: true,
        isLocked: false, // Don't show locked videos
      };

      if (search) {
        where.title = {
          contains: search,
          mode: 'insensitive',
        };
      }

      if (category) {
        where.category = category;
      }

      // Build orderBy clause
      let orderBy: any = { publishedAt: 'desc' }; // Default: recent

      if (sort === 'oldest') {
        orderBy = { publishedAt: 'asc' };
      } else if (sort === 'popular') {
        orderBy = { viewCount: 'desc' };
      }

      const videos = await prisma.video.findMany({
        where,
        select: {
          id: true,
          title: true,
          filename: true,
          durationSeconds: true,
          fileSize: true,
          width: true,
          height: true,
          orientation: true,
          quality: true,
          producer: true,
          thumbnailPath: true,
          publishedAt: true,
          category: true,
          isLocked: true,
          accessLevel: true,
          viewCount: true,
          upvoteCount: true,
          commentCount: true,
          createdAt: true,
        },
        orderBy,
        take: limit,
        skip: offset,
      });

      // Get total count
      const total = await prisma.video.count({ where });

      // Map videos to include URLs
      const videosWithUrls = videos.map((video) => ({
        ...video,
        duration: video.durationSeconds,
        thumbnailUrl: video.thumbnailPath ? `/media/videos/${video.id}/thumbnail` : null,
        videoUrl: `/media/videos/${video.id}/stream`,
      }));

      return {
        videos: videosWithUrls,
        pagination: {
          total,
          limit,
          offset,
          hasMore: offset + limit < total,
        },
      };
    }
  );

  // GET /api/public/:id - Get single published video (unauthenticated)
  fastify.get<{ Params: { id: string } }>(
    '/public/:id',
    {
      preHandler: optionalAuth,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);

      const video = await prisma.video.findFirst({
        where: {
          id: videoId,
          isPublished: true,
          isLocked: false,
        },
      });

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

      return {
        video: {
          ...video,
          duration: video.durationSeconds,
          thumbnailUrl: video.thumbnailPath ? `/media/videos/${video.id}/thumbnail` : null,
          videoUrl: `/media/videos/${video.id}/stream`,
        },
      };
    }
  );

  // GET /api/public/categories - Get list of available categories with counts
  fastify.get('/public/categories', async (request, reply) => {
    const categories = await prisma.video.groupBy({
      by: ['category'],
      where: {
        isPublished: true,
        isLocked: false,
        category: { not: null },
      },
      _count: {
        category: true,
      },
    });

    return categories.map((cat) => ({
      name: cat.category,
      count: cat._count.category,
    }));
  });

  // GET /api/public/producers - Get list of producers (public)
  fastify.get('/public/producers', async (request, reply) => {
    const videos = await prisma.video.findMany({
      where: {
        isPublished: true,
        isLocked: false,
        producer: { not: null },
      },
      select: {
        producer: true,
      },
      distinct: ['producer'],
    });

    return videos.map((v) => v.producer).filter(Boolean);
  });

  // GET /api/public/:id/thumbnail - Get video thumbnail (unauthenticated)
  fastify.get<{ Params: { id: string } }>(
    '/public/:id/thumbnail',
    {
      preHandler: optionalAuth,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);

      const video = await prisma.video.findFirst({
        where: {
          id: videoId,
          isPublished: true,
          isLocked: false,
        },
        select: {
          thumbnailPath: true,
        },
      });

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

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

      // Check file exists
      try {
        await access(thumbnailPath);
      } catch {
        return reply.code(404).send({ message: 'Thumbnail file not found' });
      }

      // Get file stats
      const stats = await new Promise<{ size: number } | null>((resolve) => {
        stat(thumbnailPath, (err, stats) => {
          if (err) resolve(null);
          else resolve({ size: stats.size });
        });
      });

      if (!stats) {
        return reply.code(404).send({ message: 'Thumbnail file not found' });
      }

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

      // Send file
      reply.header('Content-Type', mimeType);
      reply.header('Content-Length', stats.size);
      reply.header('Accept-Ranges', 'bytes');

      const stream = createReadStream(thumbnailPath);
      return reply.send(stream);
    }
  );

  // GET /api/public/:id/stream - Stream video file (unauthenticated)
  fastify.get<{ Params: { id: string } }>(
    '/public/:id/stream',
    {
      preHandler: optionalAuth,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);

      const video = await prisma.video.findFirst({
        where: {
          id: videoId,
          isPublished: true,
          isLocked: false,
        },
        select: {
          path: true,
          filename: true,
          accessLevel: true,
        },
      });

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

      // Content gating: check access level against user subscription
      if (video.accessLevel && video.accessLevel !== 'free') {
        const userId = (request as any).user?.id;
        if (!userId) {
          return reply.code(403).send({
            message: 'This content requires a subscription',
            accessLevel: video.accessLevel,
            requiresAuth: true,
          });
        }
        const subscription = await prisma.userSubscription.findFirst({
          where: {
            userId,
            status: 'active',
          },
          include: { plan: true },
        });
        if (!subscription) {
          return reply.code(403).send({
            message: 'This content requires an active subscription',
            accessLevel: video.accessLevel,
            requiresSubscription: true,
          });
        }
        // Premium content requires tier >= 2
        if (video.accessLevel === 'premium' && (subscription.plan?.tier ?? 0) < 2) {
          return reply.code(403).send({
            message: 'This content requires a premium subscription',
            accessLevel: video.accessLevel,
            requiresUpgrade: true,
          });
        }
      }

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

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

      // Get file stats
      const stats = await new Promise<{ size: number } | null>((resolve) => {
        stat(filePath, (err, stats) => {
          if (err) resolve(null);
          else resolve({ size: stats.size });
        });
      });

      if (!stats) {
        return reply.code(404).send({ message: 'Video file not found' });
      }

      const fileSize = stats.size;
      const mimeType = lookup(filePath) || 'video/mp4';
      const range = request.headers.range;

      // Handle range request (for video seeking)
      if (range) {
        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) {
          reply.header('Content-Range', `bytes */${fileSize}`);
          return reply.code(416).send({ message: 'Requested range not satisfiable' });
        }

        const chunkSize = (end - start) + 1;

        reply.code(206);
        reply.header('Content-Range', `bytes ${start}-${end}/${fileSize}`);
        reply.header('Accept-Ranges', 'bytes');
        reply.header('Content-Length', chunkSize);
        reply.header('Content-Type', mimeType);

        const stream = createReadStream(filePath, { start, end });
        return reply.send(stream);
      } else {
        // No range - send full file
        reply.header('Content-Length', fileSize);
        reply.header('Content-Type', mimeType);
        reply.header('Accept-Ranges', 'bytes');

        const stream = createReadStream(filePath);
        return reply.send(stream);
      }
    }
  );
}
