import { FastifyInstance } from 'fastify';
import { optionalAuth } from '../middleware/auth';
import { videoAnalyticsService } from '../services/video-analytics.service';
import { logger } from '../../../utils/logger';
import { z } from 'zod';
import { eventBus } from '../../../services/event-bus.service';

// Validation schemas
const recordViewSchema = z.object({
  videoId: z.number(),
  referer: z.string().optional(),
});

const recordEventSchema = z.object({
  videoId: z.number(),
  viewId: z.number().optional(),
  eventType: z.enum(['play', 'pause', 'seek', 'complete']),
  timestamp: z.number().min(0),
});

const updateWatchTimeSchema = z.object({
  viewId: z.number(),
  watchTimeSeconds: z.number().min(0),
});

export async function videoTrackingRoutes(fastify: FastifyInstance) {
  /**
   * POST /track/view
   * Record a new video view (called when video starts loading)
   * Public endpoint - no auth required, but optionally uses auth if available
   */
  fastify.post<{
    Body: { videoId: number; referer?: string };
  }>(
    '/view',
    {
      preHandler: optionalAuth,
    },
    async (request, reply) => {
      const { videoId, referer } = request.body;

      // Validate input
      try {
        recordViewSchema.parse(request.body);
      } catch (error) {
        return reply.code(400).send({ message: 'Invalid input', error });
      }

      try {
        // Get user ID if authenticated
        const userId = (request as any).user?.id;

        // Get IP address and user agent from request
        const ipAddress = request.ip;
        const userAgent = request.headers['user-agent'];

        // Record the view
        const viewId = await videoAnalyticsService.recordView({
          videoId,
          userId,
          ipAddress,
          userAgent,
          referer,
        });

        eventBus.publish('media.video.viewed', {
          videoId: String(videoId),
          videoTitle: '', // Title not available in tracking context
          userId: userId ?? null,
          sessionId: String(viewId),
        });

        return {
          success: true,
          viewId,
        };
      } catch (error) {
        logger.error('Failed to record view', { error, videoId });
        // Don't fail the request - we don't want analytics failures to break playback
        return {
          success: false,
          viewId: null,
        };
      }
    }
  );

  /**
   * POST /track/event
   * Record a video event (play, pause, seek, complete)
   * Public endpoint - no auth required
   */
  fastify.post<{
    Body: {
      videoId: number;
      viewId?: number;
      eventType: 'play' | 'pause' | 'seek' | 'complete';
      timestamp: number;
    };
  }>(
    '/event',
    async (request, reply) => {
      const { videoId, viewId, eventType, timestamp } = request.body;

      // Validate input
      try {
        recordEventSchema.parse(request.body);
      } catch (error) {
        return reply.code(400).send({ message: 'Invalid input', error });
      }

      try {
        await videoAnalyticsService.recordEvent({
          videoId,
          viewId,
          eventType,
          timestamp,
        });

        return {
          success: true,
        };
      } catch (error) {
        logger.error('Failed to record event', { error, videoId, eventType });
        // Don't fail the request - analytics failures shouldn't break playback
        return {
          success: false,
        };
      }
    }
  );

  /**
   * POST /track/heartbeat
   * Update watch time for a view (called every 10 seconds during playback)
   * Public endpoint - no auth required
   */
  fastify.post<{
    Body: {
      viewId: number;
      watchTimeSeconds: number;
    };
  }>(
    '/heartbeat',
    async (request, reply) => {
      const { viewId, watchTimeSeconds } = request.body;

      // Validate input
      try {
        updateWatchTimeSchema.parse(request.body);
      } catch (error) {
        return reply.code(400).send({ message: 'Invalid input', error });
      }

      try {
        await videoAnalyticsService.updateWatchTime(viewId, watchTimeSeconds);

        return {
          success: true,
        };
      } catch (error) {
        logger.error('Failed to update watch time', { error, viewId });
        // Don't fail the request
        return {
          success: false,
        };
      }
    }
  );

  /**
   * POST /track/batch
   * Batch record multiple events (useful for reducing requests)
   * Public endpoint - no auth required
   */
  fastify.post<{
    Body: {
      events: Array<{
        type: 'view' | 'event' | 'heartbeat';
        data: any;
      }>;
    };
  }>(
    '/batch',
    async (request, reply) => {
      const { events } = request.body;

      if (!Array.isArray(events) || events.length === 0) {
        return reply.code(400).send({ message: 'Events array is required' });
      }

      if (events.length > 50) {
        return reply.code(400).send({ message: 'Maximum 50 events per batch' });
      }

      try {
        // Validate each event's data against per-type schemas before processing
        for (const event of events) {
          switch (event.type) {
            case 'view':
              recordViewSchema.parse(event.data);
              break;
            case 'event':
              recordEventSchema.parse(event.data);
              break;
            case 'heartbeat':
              updateWatchTimeSchema.parse(event.data);
              break;
            default:
              return reply.code(400).send({ message: `Unknown event type: ${event.type}` });
          }
        }

        const results = await Promise.allSettled(
          events.map(async (event) => {
            switch (event.type) {
              case 'view':
                return videoAnalyticsService.recordView(event.data);
              case 'event':
                return videoAnalyticsService.recordEvent(event.data);
              case 'heartbeat':
                return videoAnalyticsService.updateWatchTime(
                  event.data.viewId,
                  event.data.watchTimeSeconds
                );
              default:
                throw new Error(`Unknown event type: ${event.type}`);
            }
          })
        );

        const successful = results.filter((r) => r.status === 'fulfilled').length;
        const failed = results.filter((r) => r.status === 'rejected').length;

        return {
          success: true,
          total: events.length,
          successful,
          failed,
        };
      } catch (error) {
        logger.error('Failed to process batch tracking', { error });
        return reply.code(500).send({ message: 'Failed to process batch' });
      }
    }
  );

  /**
   * GET /track/health
   * Health check for tracking endpoints
   */
  fastify.get('/health', async (request, reply) => {
    return {
      status: 'ok',
      service: 'video-tracking',
      timestamp: new Date().toISOString(),
    };
  });
}
