import { FastifyInstance } from 'fastify';
import { prisma } from '../../../config/database';
import { requireAdminRole } from '../middleware/auth';
import { videoAnalyticsService } from '../services/video-analytics.service';
import { logger } from '../../../utils/logger';
import { env } from '../../../config/env';
import { signMediaPath } from '../../../utils/signed-url';
import { copyFile } from 'fs/promises';
import { join, dirname, basename, extname, normalize, resolve } from 'path';
import { z } from 'zod';

const UpdateVideoSchema = z.object({
  title: z.string().min(1).max(500).optional(),
  producer: z.string().max(200).nullable().optional(),
  creator: z.string().max(200).nullable().optional(),
  category: z.enum(['videos', 'curated', 'compilations', 'playback', 'highlights']).nullable().optional(),
  tags: z.array(z.string().max(100)).max(50).nullable().optional(),
  quality: z.string().max(50).nullable().optional(),
  position: z.number().int().min(0).nullable().optional(),
  isShort: z.boolean().optional(),
  accessLevel: z.enum(['free', 'member', 'premium']).optional(),
});

export async function videoActionsRoutes(fastify: FastifyInstance) {
  /**
   * PATCH /videos/:id
   * Update video metadata
   */
  fastify.patch<{ Params: { id: string } }>(
    '/:id',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);
      const parseResult = UpdateVideoSchema.safeParse(request.body);

      if (!parseResult.success) {
        return reply.code(400).send({ message: 'Invalid input', errors: parseResult.error.errors });
      }

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

        const data: any = {};
        const updates = parseResult.data;

        if (updates.title !== undefined) data.title = updates.title;
        if (updates.producer !== undefined) data.producer = updates.producer;
        if (updates.creator !== undefined) data.creator = updates.creator;
        if (updates.category !== undefined) data.category = updates.category;
        if (updates.tags !== undefined) data.tags = updates.tags;
        if (updates.quality !== undefined) data.quality = updates.quality;
        if (updates.position !== undefined) data.position = updates.position;
        if (updates.isShort !== undefined) data.isShort = updates.isShort;
        if (updates.accessLevel !== undefined) data.accessLevel = updates.accessLevel;

        const updatedVideo = await prisma.video.update({
          where: { id: videoId },
          data,
        });

        logger.info(`Updated video ${videoId} metadata`, { fields: Object.keys(data) });

        return {
          success: true,
          video: {
            ...updatedVideo,
            duration: updatedVideo.durationSeconds,
            thumbnailUrl: updatedVideo.thumbnailPath ? `/media/videos/${updatedVideo.id}/thumbnail` : null,
          },
        };
      } catch (error) {
        logger.error('Failed to update video', { error, videoId });
        return reply.code(500).send({ message: 'Failed to update video' });
      }
    }
  );

  /**
   * POST /videos/:id/duplicate
   * Duplicate a video with a new title
   */
  fastify.post<{ Params: { id: string }; Body: { title?: string } }>(
    '/:id/duplicate',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);
      const { title } = request.body || {};

      try {
        const originalVideo = await prisma.video.findUnique({
          where: { id: videoId },
        });

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

        // Create duplicate with new title
        const duplicateTitle = title || `${originalVideo.title || originalVideo.filename} (Copy)`;

        const duplicate = await prisma.video.create({
          data: {
            path: originalVideo.path, // Same file path
            filename: originalVideo.filename,
            producer: originalVideo.producer,
            creator: originalVideo.creator,
            title: duplicateTitle,
            durationSeconds: originalVideo.durationSeconds,
            quality: originalVideo.quality,
            orientation: originalVideo.orientation,
            hasAudio: originalVideo.hasAudio,
            fileSize: originalVideo.fileSize,
            fileHash: originalVideo.fileHash,
            width: originalVideo.width,
            height: originalVideo.height,
            thumbnailPath: originalVideo.thumbnailPath,
            tags: originalVideo.tags as any,
            directoryType: originalVideo.directoryType,
            category: originalVideo.category,
            uploaderId: originalVideo.uploaderId,
          },
        });

        logger.info(`Duplicated video ${videoId} to ${duplicate.id}`, { originalTitle: originalVideo.title, newTitle: duplicateTitle });

        return {
          success: true,
          duplicate: {
            id: duplicate.id,
            title: duplicate.title,
          },
        };
      } catch (error) {
        logger.error('Failed to duplicate video', { error, videoId });
        return reply.code(500).send({ message: 'Failed to duplicate video' });
      }
    }
  );

  /**
   * POST /videos/:id/replace
   * Replace video file while keeping metadata and URL
   * Note: This endpoint accepts a new file path - actual file upload should go through upload routes
   */
  const ReplaceVideoSchema = z.object({
    newPath: z.string().min(1).max(500),
    newFilename: z.string().min(1).max(255),
    durationSeconds: z.number().optional(),
    width: z.number().int().optional(),
    height: z.number().int().optional(),
    fileSize: z.number().optional(),
  });

  fastify.post<{
    Params: { id: string };
    Body: z.infer<typeof ReplaceVideoSchema>;
  }>(
    '/:id/replace',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);

      // Validate input with Zod
      const parseResult = ReplaceVideoSchema.safeParse(request.body);
      if (!parseResult.success) {
        return reply.code(400).send({ message: 'Invalid input' });
      }
      const { newPath, newFilename, durationSeconds, width, height, fileSize } = parseResult.data;

      // Path traversal protection: resolve against allowed base and verify containment
      const MEDIA_BASE = '/media/local';
      if (newPath.includes('\0') || newFilename.includes('\0')) {
        return reply.code(400).send({ message: 'Invalid file path' });
      }
      const resolvedPath = resolve(MEDIA_BASE, newPath);
      if (!resolvedPath.startsWith(resolve(MEDIA_BASE))) {
        return reply.code(400).send({ message: 'Invalid file path: must be within media directory' });
      }
      const sanitizedFilename = basename(newFilename);

      try {
        const existingVideo = await prisma.video.findUnique({
          where: { id: videoId },
        });

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

        // Update video with new file details
        const updatedVideo = await prisma.video.update({
          where: { id: videoId },
          data: {
            path: resolvedPath,
            filename: sanitizedFilename,
            originalPath: existingVideo.path, // Save old path for reference
            originalFilename: existingVideo.filename,
            durationSeconds: durationSeconds || existingVideo.durationSeconds,
            width: width || existingVideo.width,
            height: height || existingVideo.height,
            fileSize: fileSize ? BigInt(fileSize) : existingVideo.fileSize,
            lastValidated: new Date(),
            thumbnailPath: null, // Clear thumbnail, will be regenerated
          },
        });

        logger.info(`Replaced video file for ${videoId}`, { oldPath: existingVideo.path, newPath });

        return {
          success: true,
          video: {
            id: updatedVideo.id,
            title: updatedVideo.title,
            filename: updatedVideo.filename,
          },
        };
      } catch (error) {
        logger.error('Failed to replace video', { error, videoId });
        return reply.code(500).send({ message: 'Failed to replace video' });
      }
    }
  );

  /**
   * GET /videos/:id/analytics
   * Get detailed analytics for a video
   */
  fastify.get<{
    Params: { id: string };
    Querystring: { startDate?: string; endDate?: string };
  }>(
    '/:id/analytics',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);
      const { startDate, endDate } = request.query;

      // Validate date parameters
      const parsedStart = startDate ? new Date(startDate) : undefined;
      const parsedEnd = endDate ? new Date(endDate) : undefined;
      if ((parsedStart && isNaN(parsedStart.getTime())) || (parsedEnd && isNaN(parsedEnd.getTime()))) {
        return reply.code(400).send({ message: 'Invalid date format for startDate or endDate' });
      }

      try {
        const analytics = await videoAnalyticsService.getVideoAnalytics(
          videoId,
          parsedStart,
          parsedEnd,
        );

        return analytics;
      } catch (error) {
        logger.error('Failed to get video analytics', { error, videoId });
        if (error instanceof Error && error.message === 'Video not found') {
          return reply.code(404).send({ message: 'Video not found' });
        }
        return reply.code(500).send({ message: 'Failed to fetch analytics' });
      }
    }
  );

  /**
   * POST /videos/:id/reset-analytics
   * Reset all analytics for a video
   */
  fastify.post<{ Params: { id: string } }>(
    '/:id/reset-analytics',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);

      try {
        await videoAnalyticsService.resetAnalytics(videoId);

        return {
          success: true,
          message: 'Analytics reset successfully',
        };
      } catch (error) {
        logger.error('Failed to reset analytics', { error, videoId });
        return reply.code(500).send({ message: 'Failed to reset analytics' });
      }
    }
  );

  /**
   * GET /videos/:id/preview-link
   * Generate a shareable, time-limited preview link.
   *
   * Uses path-bound HMAC signatures (sig/exp/uid) — same scheme as
   * POST /api/media/sign — instead of the legacy `?token=<JWT>` form,
   * which leaked full session tokens via access logs/referer headers.
   * The signature carries only the admin's user-id and is bound to the
   * stream URL, so it can be safely shared with stakeholders.
   */
  fastify.get<{ Params: { id: string } }>(
    '/:id/preview-link',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const videoId = parseInt(request.params.id);

      try {
        const video = await prisma.video.findUnique({
          where: { id: videoId },
        });

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

        const expiryHours = parseInt(process.env.VIDEO_PREVIEW_LINK_EXPIRY_HOURS || '24');
        const ttlSeconds = expiryHours * 60 * 60;
        const userId = request.user!.id;

        const streamPath = `/api/videos/${videoId}/stream`;
        const signed = signMediaPath(streamPath, userId, ttlSeconds);
        const query = `sig=${signed.sig}&exp=${signed.exp}&uid=${signed.uid}`;
        const previewUrl = `${env.MEDIA_API_PUBLIC_URL}${streamPath}?${query}`;

        logger.info(`Generated preview link for video ${videoId}`, { expiresInHours: expiryHours });

        return {
          previewUrl,
          expiresAt: new Date(Number(signed.exp) * 1000).toISOString(),
          expiryHours,
        };
      } catch (error) {
        logger.error('Failed to generate preview link', { error, videoId });
        return reply.code(500).send({ message: 'Failed to generate preview link' });
      }
    }
  );

  /**
   * GET /videos/analytics/top
   * Get top performing videos
   */
  fastify.get<{
    Querystring: { metric?: 'views' | 'watchTime'; limit?: string };
  }>(
    '/analytics/top',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const metric = request.query.metric || 'views';
      const limit = parseInt(request.query.limit || '10');

      try {
        const topVideos = await videoAnalyticsService.getTopVideos(metric, limit);

        return {
          metric,
          videos: topVideos,
        };
      } catch (error) {
        logger.error('Failed to get top videos', { error, metric });
        return reply.code(500).send({ message: 'Failed to fetch top videos' });
      }
    }
  );

  /**
   * GET /videos/analytics/overview
   * Get global analytics overview across all videos
   */
  fastify.get(
    '/analytics/overview',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      try {
        const [totalVideos, totalViews, totalWatchTime, avgCompletionRate] = await Promise.all([
          prisma.video.count(),
          prisma.video.aggregate({
            _sum: {
              viewCount: true,
            },
          }),
          prisma.video.aggregate({
            _sum: {
              totalWatchTimeSeconds: true,
            },
          }),
          prisma.video.aggregate({
            _avg: {
              completionRate: true,
            },
          }),
        ]);

        return {
          totalVideos,
          totalViews: totalViews._sum.viewCount || 0,
          totalWatchTimeSeconds: totalWatchTime._sum.totalWatchTimeSeconds || 0,
          averageCompletionRate: avgCompletionRate._avg.completionRate?.toNumber() || 0,
        };
      } catch (error) {
        logger.error('Failed to get analytics overview', { error });
        return reply.code(500).send({ message: 'Failed to fetch analytics overview' });
      }
    }
  );

  /**
   * POST /videos/bulk-access-level
   * Set access level on multiple videos at once
   */
  fastify.post<{
    Body: { videoIds: number[]; accessLevel: string };
  }>(
    '/bulk-access-level',
    {
      preHandler: requireAdminRole,
    },
    async (request, reply) => {
      const schema = z.object({
        videoIds: z.array(z.number().int()).min(1).max(500),
        accessLevel: z.enum(['free', 'member', 'premium']),
      });

      const parseResult = schema.safeParse(request.body);
      if (!parseResult.success) {
        return reply.code(400).send({ message: 'Invalid input', errors: parseResult.error.errors });
      }

      const { videoIds, accessLevel } = parseResult.data;

      try {
        const result = await prisma.video.updateMany({
          where: { id: { in: videoIds } },
          data: { accessLevel },
        });

        logger.info(`Bulk updated access level to "${accessLevel}" for ${result.count} videos`, { videoIds });

        return {
          success: true,
          updatedCount: result.count,
        };
      } catch (error) {
        logger.error('Failed to bulk update access level', { error, videoIds });
        return reply.code(500).send({ message: 'Failed to update access levels' });
      }
    }
  );
}
