import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { pipeline } from 'stream/promises';
import { createWriteStream } from 'fs';
import { unlink, mkdir } from 'fs/promises';
import { join, extname } from 'path';
import { randomUUID } from 'crypto';
import { prisma } from '../../../config/database';
import { extractVideoMetadata, validateVideoFile } from '../services/ffprobe.service';
import { ThumbnailService } from '../services/thumbnail.service';
import { hlsTranscodeQueueService } from '../../../services/hls-transcode-queue.service';
import { logger } from '../../../utils/logger';
import { z } from 'zod';
import { requireAdminRole } from '../middleware/auth';

// Allowed video extensions
const ALLOWED_EXTENSIONS = ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.m4v', '.flv'];

// Zod schema for upload metadata
const UploadMetadataSchema = z.object({
  title: z.string().optional(),
  producer: z.string().optional(),
  creator: z.string().optional(),
});

/**
 * Upload a single video file
 */
async function uploadVideo(request: FastifyRequest, reply: FastifyReply) {
  let tempFilePath: string | null = null;

  try {

    // Get the uploaded file
    const data = await request.file();
    if (!data) {
      return reply.code(400).send({ message: 'No file uploaded' });
    }

    // Validate file extension
    const ext = extname(data.filename).toLowerCase();
    if (!ALLOWED_EXTENSIONS.includes(ext)) {
      return reply.code(400).send({
        message: `Invalid file type. Allowed: ${ALLOWED_EXTENSIONS.join(', ')}`,
      });
    }

    // Generate unique filename
    const filename = `${randomUUID()}${ext}`;
    const inboxDir = '/media/local/inbox';

    // Ensure inbox directory exists
    await mkdir(inboxDir, { recursive: true });

    const filePath = join(inboxDir, filename);
    tempFilePath = filePath;

    // Stream file to disk (must consume file stream BEFORE reading metadata fields,
    // because Fastify multipart/busboy only makes fields available after the file stream ends)
    logger.info(`Uploading video to ${filePath}`);
    await pipeline(data.file, createWriteStream(filePath));

    // Extract metadata fields from form data (now available after file stream consumed)
    const metadataFields = data.fields as Record<string, { value: string }>;
    const metadata = {
      title: metadataFields.title?.value,
      producer: metadataFields.producer?.value,
      creator: metadataFields.creator?.value,
    };

    // Validate metadata
    const validatedMetadata = UploadMetadataSchema.parse(metadata);

    // Validate video file
    logger.info(`Validating video file: ${filePath}`);
    const isValid = await validateVideoFile(filePath);
    if (!isValid) {
      await unlink(filePath);
      return reply.code(400).send({ message: 'Invalid or corrupted video file' });
    }

    // Extract metadata
    logger.info(`Extracting metadata from: ${filePath}`);
    const videoMetadata = await extractVideoMetadata(filePath);

    // Insert into database
    const video = await prisma.video.create({
      data: {
        path: filePath, // Store full path: /media/local/inbox/uuid.mp4
        filename,
        originalFilename: data.filename,
        title: validatedMetadata.title || data.filename,
        durationSeconds: videoMetadata.durationSeconds,
        width: videoMetadata.width,
        height: videoMetadata.height,
        orientation: videoMetadata.orientation,
        quality: videoMetadata.quality,
        hasAudio: videoMetadata.hasAudio,
        fileSize: videoMetadata.fileSize,
        directoryType: 'inbox',
        isValid: true,
        isShort: videoMetadata.durationSeconds != null && videoMetadata.durationSeconds <= 60,
        producer: validatedMetadata.producer || null,
        creator: validatedMetadata.creator || null,
      },
    });

    logger.info(`Video uploaded successfully: ${video.id}`);

    // Generate thumbnail
    try {
      const thumbnailPath = await ThumbnailService.generateThumbnail({
        videoPath: filePath,
        videoId: video.id,
        duration: videoMetadata.durationSeconds,
        orientation: videoMetadata.orientation,
      });

      // Update video with thumbnail path
      await prisma.video.update({
        where: { id: video.id },
        data: { thumbnailPath },
      });

      logger.info(`Thumbnail generated for video ${video.id}`);
    } catch (thumbnailError) {
      // Log error but don't fail the upload
      logger.error(`Failed to generate thumbnail for video ${video.id}:`, thumbnailError);
    }

    // Enqueue HLS transcode (no-op when ENABLE_HLS_TRANSCODE=false; sets SKIPPED).
    try {
      await hlsTranscodeQueueService.submitTranscode(video.id);
    } catch (hlsErr) {
      logger.error(`Failed to enqueue HLS transcode for video ${video.id}:`, hlsErr);
    }

    return reply.code(201).send({
      message: 'Video uploaded successfully',
      video,
    });
  } catch (error) {
    // Clean up file on error
    if (tempFilePath) {
      try {
        await unlink(tempFilePath);
      } catch (cleanupError) {
        logger.error('Failed to clean up file after error:', cleanupError);
      }
    }

    logger.error('Video upload failed:', error);

    if (error instanceof z.ZodError) {
      return reply.code(400).send({
        message: 'Invalid metadata',
        errors: error.errors,
      });
    }

    return reply.code(500).send({
      message: error instanceof Error ? error.message : 'Upload failed',
    });
  }
}

/**
 * Upload multiple video files in batch
 */
async function uploadBatch(request: FastifyRequest, reply: FastifyReply) {
  try {
    const files = request.files();
    const results: Array<{ filename: string; success: boolean; error?: string; video?: any }> = [];

    for await (const file of files) {
      let tempFilePath: string | null = null;

      try {
        // Validate file extension
        const ext = extname(file.filename).toLowerCase();
        if (!ALLOWED_EXTENSIONS.includes(ext)) {
          results.push({
            filename: file.filename,
            success: false,
            error: `Invalid file type. Allowed: ${ALLOWED_EXTENSIONS.join(', ')}`,
          });
          continue;
        }

        // Generate unique filename
        const filename = `${randomUUID()}${ext}`;
        const inboxDir = '/media/local/inbox';

        // Ensure inbox directory exists
        await mkdir(inboxDir, { recursive: true });

        const filePath = join(inboxDir, filename);
        tempFilePath = filePath;

        // Stream file to disk
        await pipeline(file.file, createWriteStream(filePath));

        // Validate video file
        const isValid = await validateVideoFile(filePath);
        if (!isValid) {
          await unlink(filePath);
          results.push({
            filename: file.filename,
            success: false,
            error: 'Invalid or corrupted video file',
          });
          continue;
        }

        // Extract metadata
        const videoMetadata = await extractVideoMetadata(filePath);

        // Insert into database
        const video = await prisma.video.create({
          data: {
            path: filePath, // Store full path: /media/local/inbox/uuid.mp4
            filename,
            originalFilename: file.filename,
            title: file.filename,
            durationSeconds: videoMetadata.durationSeconds,
            width: videoMetadata.width,
            height: videoMetadata.height,
            orientation: videoMetadata.orientation,
            quality: videoMetadata.quality,
            hasAudio: videoMetadata.hasAudio,
            fileSize: videoMetadata.fileSize,
            directoryType: 'inbox',
            isValid: true,
            isShort: videoMetadata.durationSeconds != null && videoMetadata.durationSeconds <= 60,
          },
        });

        // Generate thumbnail
        try {
          const thumbnailPath = await ThumbnailService.generateThumbnail({
            videoPath: filePath,
            videoId: video.id,
            duration: videoMetadata.durationSeconds,
            orientation: videoMetadata.orientation,
          });

          // Update video with thumbnail path
          await prisma.video.update({
            where: { id: video.id },
            data: { thumbnailPath },
          });

          logger.info(`Thumbnail generated for video ${video.id}`);
        } catch (thumbnailError) {
          // Log error but don't fail the upload
          logger.error(`Failed to generate thumbnail for video ${video.id}:`, thumbnailError);
        }

        // Enqueue HLS transcode (no-op when flag off).
        try {
          await hlsTranscodeQueueService.submitTranscode(video.id);
        } catch (hlsErr) {
          logger.error(`Failed to enqueue HLS transcode for video ${video.id}:`, hlsErr);
        }

        results.push({
          filename: file.filename,
          success: true,
          video,
        });

        logger.info(`Batch upload successful: ${file.filename} -> ${video.id}`);
      } catch (error) {
        // Clean up file on error
        if (tempFilePath) {
          try {
            await unlink(tempFilePath);
          } catch (cleanupError) {
            logger.error('Failed to clean up file after error:', cleanupError);
          }
        }

        logger.error(`Batch upload failed for ${file.filename}:`, error);

        results.push({
          filename: file.filename,
          success: false,
          error: error instanceof Error ? error.message : 'Upload failed',
        });
      }
    }

    const successCount = results.filter((r) => r.success).length;
    const failCount = results.length - successCount;

    return reply.code(207).send({
      message: `Batch upload complete: ${successCount} succeeded, ${failCount} failed`,
      results,
    });
  } catch (error) {
    logger.error('Batch upload failed:', error);
    return reply.code(500).send({
      message: error instanceof Error ? error.message : 'Batch upload failed',
    });
  }
}

export async function uploadRoutes(fastify: FastifyInstance) {
  // Single file upload
  fastify.post(
    '/upload',
    {
      preHandler: requireAdminRole,
    },
    uploadVideo
  );

  // Batch upload
  fastify.post(
    '/upload/batch',
    {
      preHandler: requireAdminRole,
    },
    uploadBatch
  );
}
