import { Router, Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { redis } from '../../config/redis';
import { prisma } from '../../config/database';
import { env } from '../../config/env';
import { siteSettingsService } from '../settings/settings.service';
import { logger } from '../../utils/logger';
import { eventSubmissionRateLimit } from '../../middleware/rate-limit';
import { unifiedCalendarService } from './unified-calendar.service';
import { gancioClient } from '../../services/gancio.client';

const router = Router();

const CACHE_KEY = 'events:public-list';
const CACHE_TTL = 300; // 5 minutes

export interface PublicEvent {
  id: number;
  title: string;
  description: string;
  placeName: string;
  placeAddress: string;
  startDatetime: string;
  endDatetime: string | null;
  tags: string[];
  shiftId: string | null;
}

// GET /api/events/public?limit=20&upcoming=true
router.get('/public', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const settings = await siteSettingsService.getPublic();
    if (!settings.enableEvents) {
      res.json([]);
      return;
    }

    const limit = Math.min(parseInt(req.query.limit as string) || 20, 50);
    const upcoming = req.query.upcoming !== 'false';

    // Check cache
    const cacheKey = `${CACHE_KEY}:${upcoming}:${limit}`;
    try {
      const cached = await redis.get(cacheKey);
      if (cached) {
        res.json(JSON.parse(cached));
        return;
      }
    } catch { /* cache miss */ }

    // Fetch from Gancio public API (no auth needed)
    let rawEvents: Array<{
      id: number;
      title: string;
      description: string;
      place_name: string;
      place_address: string;
      start_datetime: number;
      end_datetime?: number;
      tags: string[];
    }>;

    try {
      const url = `${env.GANCIO_URL}/api/events`;
      const fetchRes = await fetch(url, { signal: AbortSignal.timeout(5000) });
      if (!fetchRes.ok) {
        res.json([]);
        return;
      }
      rawEvents = await fetchRes.json() as typeof rawEvents;
    } catch (err) {
      logger.debug('Failed to fetch events from Gancio:', err);
      res.json([]);
      return;
    }

    const nowUnix = Math.floor(Date.now() / 1000);
    let filtered = rawEvents;
    if (upcoming) {
      filtered = rawEvents.filter(e => e.start_datetime >= nowUnix);
    }
    filtered.sort((a, b) => a.start_datetime - b.start_datetime);
    filtered = filtered.slice(0, limit);

    // Batch-load matching shifts
    const gancioIds = filtered.map(e => e.id);
    const shifts = await prisma.shift.findMany({
      where: { gancioEventId: { in: gancioIds } },
      select: { id: true, gancioEventId: true },
    });
    const shiftMap = new Map(shifts.map(s => [s.gancioEventId!, s.id]));

    const events: PublicEvent[] = filtered.map(e => ({
      id: e.id,
      title: e.title,
      description: (e.description || '').slice(0, 300),
      placeName: e.place_name || '',
      placeAddress: e.place_address || '',
      startDatetime: new Date(e.start_datetime * 1000).toISOString(),
      endDatetime: e.end_datetime ? new Date(e.end_datetime * 1000).toISOString() : null,
      tags: Array.isArray(e.tags) ? e.tags : [],
      shiftId: shiftMap.get(e.id) ?? null,
    }));

    // Cache
    try {
      await redis.setex(cacheKey, CACHE_TTL, JSON.stringify(events));
    } catch { /* non-critical */ }

    res.json(events);
  } catch (err) {
    next(err);
  }
});

// --- Unified Calendar ---

const calendarQuerySchema = z.object({
  startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be YYYY-MM-DD'),
  endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be YYYY-MM-DD'),
}).refine(data => {
  const start = new Date(data.startDate);
  const end = new Date(data.endDate);
  const diffDays = (end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
  return diffDays >= 0 && diffDays <= 90;
}, { message: 'Date range must be 0-90 days' });

// GET /api/events/calendar?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD
router.get('/calendar', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const settings = await siteSettingsService.getPublic();
    if (!settings.enableEvents) {
      res.json({ dates: {} });
      return;
    }

    const parsed = calendarQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      res.status(400).json({
        error: { message: 'Invalid date range', code: 'VALIDATION_ERROR' },
      });
      return;
    }

    const result = await unifiedCalendarService.getCalendar(
      parsed.data.startDate,
      parsed.data.endDate,
    );
    res.json(result);
  } catch (err) {
    next(err);
  }
});

// --- Event Submission (Proxy to Gancio) ---

const eventSubmitSchema = z.object({
  title: z.string().min(1).max(200),
  description: z.string().max(2000).optional(),
  date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be YYYY-MM-DD'),
  startTime: z.string().regex(/^\d{2}:\d{2}$/, 'Must be HH:MM'),
  endTime: z.string().regex(/^\d{2}:\d{2}$/, 'Must be HH:MM'),
  location: z.string().max(500).optional(),
  tags: z.array(z.string().max(50)).max(10).optional(),
});

// POST /api/events/submit
router.post('/submit', eventSubmissionRateLimit, async (req: Request, res: Response, next: NextFunction) => {
  try {
    const settings = await siteSettingsService.getPublic();
    if (!settings.enableEvents) {
      res.status(403).json({
        error: { message: 'Events are not enabled', code: 'EVENTS_DISABLED' },
      });
      return;
    }

    if (!gancioClient.enabled) {
      res.status(503).json({
        error: { message: 'Event service is not configured', code: 'GANCIO_NOT_CONFIGURED' },
      });
      return;
    }

    const parsed = eventSubmitSchema.safeParse(req.body);
    if (!parsed.success) {
      res.status(400).json({
        error: { message: 'Invalid event data', code: 'VALIDATION_ERROR' },
      });
      return;
    }

    const { title, description, date, startTime, endTime, location, tags } = parsed.data;

    // Ensure 'community' tag is always included
    const eventTags = [...new Set([...(tags || []), 'community'])];

    const gancioEventId = await gancioClient.createEvent({
      title,
      description,
      location,
      date: new Date(date),
      startTime,
      endTime,
      tags: eventTags,
    });

    if (gancioEventId === null) {
      res.status(502).json({
        error: { message: 'Failed to create event in calendar service', code: 'GANCIO_ERROR' },
      });
      return;
    }

    // Bust calendar cache so the new event appears
    await unifiedCalendarService.bustCache();

    res.status(201).json({ success: true, gancioEventId });
  } catch (err) {
    next(err);
  }
});

export { router as eventsListPublicRouter };
