import { FastifyInstance, FastifyRequest } from 'fastify';
import { randomUUID } from 'crypto';
import { prisma } from '../../../config/database';
import { broadcastCommentToVideo } from './chat-stream.routes.js';
import { optionalAuth } from '../middleware/auth';
import { checkContent } from '../services/word-filter.service';
import { notifyUser } from './chat-notifications.routes';
import { logger } from '../../../utils/logger';

// Rate limiting map: userId/sessionId -> array of timestamps
const commentRateLimitMap = new Map<string, number[]>();
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX = 5; // 5 comments per minute

interface CreateCommentBody {
  content: string;
}

interface GetCommentsQuery {
  limit?: string;
  offset?: string;
}

export async function commentsRoutes(fastify: FastifyInstance) {
  /**
   * GET /public/:id/comments
   * List comments for a video (non-hidden, paginated)
   */
  fastify.get(
    '/public/:id/comments',
    async (
      request: FastifyRequest<{
        Params: { id: string };
        Querystring: GetCommentsQuery;
      }>,
      reply
    ) => {
      try {
        const videoId = parseInt(request.params.id, 10);
        const limit = parseInt(request.query.limit || '50', 10);
        const offset = parseInt(request.query.offset || '0', 10);

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

        // Fetch comments with user info (if userId is set)
        const comments = await prisma.comment.findMany({
          where: {
            mediaId: videoId,
            isHidden: false,
          },
          include: {
            user: {
              select: {
                id: true,
                email: true,
                name: true,
              },
            },
          },
          orderBy: {
            createdAt: 'asc',
          },
          take: limit,
          skip: offset,
        });

        // Transform for frontend
        const transformedComments = comments.map((comment) => ({
          id: comment.id,
          content: comment.content,
          createdAt: comment.createdAt.toISOString(),
          safetyStatus: comment.safetyStatus,
          safetyCategories: comment.safetyCategories,
          user: comment.user
            ? {
                id: comment.user.id,
                name: comment.user.name || 'Anonymous',
              }
            : null,
        }));

        return reply.send({
          comments: transformedComments,
          total: await prisma.comment.count({
            where: { mediaId: videoId, isHidden: false },
          }),
        });
      } catch (error) {
        logger.error('Failed to fetch comments:', error);
        return reply.code(500).send({ message: 'Failed to fetch comments' });
      }
    }
  );

  /**
   * POST /public/:id/comments
   * Create a new comment (rate limited, requires session or auth)
   */
  fastify.post(
    '/public/:id/comments',
    async (
      request: FastifyRequest<{
        Params: { id: string };
        Body: CreateCommentBody;
      }>,
      reply
    ) => {
      // Optionally authenticate (attaches request.user if Bearer token present)
      await optionalAuth(request, reply);

      try {
        const videoId = parseInt(request.params.id, 10);
        const { content } = request.body;

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

        if (!content || content.trim().length === 0) {
          return reply.code(400).send({ message: 'Comment content is required' });
        }

        if (content.length > 1000) {
          return reply.code(400).send({
            message: 'Comment must be 1000 characters or less',
          });
        }

        // Get session ID from X-Session-ID header (set by frontend)
        let sessionId = request.headers['x-session-id'] as string | undefined;
        let userId: string | null = null;

        // Check if user is authenticated (from optionalAuth preHandler)
        if (request.user) {
          userId = request.user.id;
        }

        // If no session ID from header, generate one
        if (!sessionId) {
          sessionId = randomUUID();
        }

        // Ensure session record exists
        await prisma.session.upsert({
          where: { id: sessionId },
          update: {},
          create: {
            id: sessionId,
            ipAddress: request.ip,
            userAgent: request.headers['user-agent'] || '',
          },
        });

        // Rate limiting check — use IP for anonymous users to prevent header-based bypass
        const rateLimitKey = userId || `ip:${request.ip}`;
        const now = Date.now();
        const timestamps = commentRateLimitMap.get(rateLimitKey) || [];
        const recentTimestamps = timestamps.filter(
          (ts) => now - ts < RATE_LIMIT_WINDOW
        );

        if (recentTimestamps.length >= RATE_LIMIT_MAX) {
          return reply.code(429).send({
            message: `Rate limit exceeded. Maximum ${RATE_LIMIT_MAX} comments per minute.`,
          });
        }

        recentTimestamps.push(now);
        commentRateLimitMap.set(rateLimitKey, recentTimestamps);

        // Run word filter check
        const filterResult = await checkContent(content.trim());

        // High-severity words: block submission entirely
        if (filterResult.blocked) {
          return reply.code(400).send({
            message: 'Your comment contains content that is not allowed.',
          });
        }

        // Determine safety status and hidden state based on filter result
        let safetyStatus = 'pending';
        let isHidden = false;
        let hiddenReason: string | null = null;

        if (filterResult.autoHide) {
          // Medium-severity: save but auto-hide
          safetyStatus = 'flagged';
          isHidden = true;
          hiddenReason = 'word_filter';
        } else if (filterResult.flagged) {
          // Low-severity: visible but flagged for review
          safetyStatus = 'flagged';
        }

        // Create comment
        const newComment = await prisma.comment.create({
          data: {
            mediaId: videoId,
            sessionId,
            userId,
            content: content.trim(),
            safetyStatus,
            isHidden,
            hiddenReason,
            safetyReasoning: filterResult.reason || null,
            safetyCategories: filterResult.matchedWords.length > 0
              ? (filterResult.matchedWords as any)
              : undefined,
          },
          include: {
            user: {
              select: {
                id: true,
                email: true,
                name: true,
              },
            },
          },
        });

        // Broadcast to SSE subscribers (only if not hidden)
        const broadcastData = {
          id: newComment.id,
          content: newComment.content,
          createdAt: newComment.createdAt.toISOString(),
          safetyStatus: newComment.safetyStatus,
          user: newComment.user
            ? {
                id: newComment.user.id,
                name: newComment.user.name || 'Anonymous',
              }
            : null,
        };

        if (!isHidden) {
          broadcastCommentToVideo(videoId, broadcastData);

          // Notify other users who commented on this video
          try {
            const otherCommenters = await prisma.comment.findMany({
              where: {
                mediaId: videoId,
                userId: { not: null, ...(userId ? { not: userId } : {}) },
              },
              select: { userId: true },
              distinct: ['userId'],
            });

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

            const commenterName = newComment.user?.name || 'Someone';
            const contentPreview = content.trim().length > 80
              ? content.trim().substring(0, 80) + '...'
              : content.trim();

            for (const { userId: targetUserId } of otherCommenters) {
              if (targetUserId) {
                notifyUser(targetUserId, {
                  type: 'chat_reply',
                  videoId,
                  videoTitle: video?.filename || `Video #${videoId}`,
                  commentId: newComment.id,
                  commenterName,
                  contentPreview,
                });
              }
            }
          } catch (notifyErr) {
            // Non-critical: don't fail the comment creation
            logger.error('Failed to send chat notifications:', notifyErr);
          }
        }

        return reply.code(201).send(broadcastData);
      } catch (error) {
        logger.error('Failed to create comment:', error);
        return reply.code(500).send({ message: 'Failed to create comment' });
      }
    }
  );

  /**
   * Cleanup rate limit map periodically (every 5 minutes)
   */
  setInterval(() => {
    const now = Date.now();
    for (const [key, timestamps] of commentRateLimitMap.entries()) {
      const recentTimestamps = timestamps.filter(
        (ts) => now - ts < RATE_LIMIT_WINDOW
      );
      if (recentTimestamps.length === 0) {
        commentRateLimitMap.delete(key);
      } else {
        commentRateLimitMap.set(key, recentTimestamps);
      }
    }
  }, 5 * 60 * 1000);
}
