import { Request, Response } from 'express';
import { ZodError } from 'zod';
import bookingService from '../services/booking.service';
import vehicleService from '../services/vehicle.service';
import pool from '../config/database';
import { permissionService } from '../services/permission.service';
import {
  CreateBuchungSchema,
  UpdateBuchungSchema,
  CancelBuchungSchema,
} from '../models/booking.model';
import logger from '../utils/logger';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function isValidUUID(s: string): boolean {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s);
}

function handleZodError(res: Response, err: ZodError): void {
  res.status(400).json({
    success: false,
    message: 'Validierungsfehler',
    errors: err.flatten().fieldErrors,
  });
}

function handleConflictError(res: Response, err: Error): boolean {
  if (err.message?.includes('außer Dienst')) {
    res.status(409).json({ success: false, message: err.message, reason: 'out_of_service' });
    return true;
  }
  if (err.message?.includes('bereits gebucht')) {
    res.status(409).json({ success: false, message: err.message, reason: 'booking_conflict' });
    return true;
  }
  return false;
}

// ---------------------------------------------------------------------------
// Controller
// ---------------------------------------------------------------------------

class BookingController {
  /**
   * GET /api/bookings/vehicles
   * Lightweight vehicle list for the booking form (no fahrzeuge:view needed).
   */
  async getVehiclesForBooking(_req: Request, res: Response): Promise<void> {
    try {
      const result = await pool.query(
        'SELECT id, bezeichnung, amtliches_kennzeichen FROM fahrzeuge ORDER BY bezeichnung'
      );
      res.json({ success: true, data: result.rows });
    } catch (err) {
      logger.error('Failed to fetch vehicles for booking', err);
      res.status(500).json({ success: false, message: 'Fehler beim Laden der Fahrzeuge' });
    }
  }

  /**
   * GET /api/bookings/calendar?from=&to=&fahrzeugId=
   * Returns all non-cancelled bookings overlapping the given date range.
   */
  async getCalendarRange(req: Request, res: Response): Promise<void> {
    try {
      const { from, to, fahrzeugId } = req.query;
      if (!from || !to) {
        res.status(400).json({ success: false, message: 'from und to sind erforderlich' });
        return;
      }
      const fromDate = new Date(from as string);
      const toDate = new Date(to as string);
      const [bookings, maintenanceWindows] = await Promise.all([
        bookingService.getBookingsByRange(fromDate, toDate, fahrzeugId as string | undefined),
        vehicleService.getMaintenanceWindows(fromDate, toDate),
      ]);
      res.json({ success: true, data: { bookings, maintenanceWindows } });
    } catch (error) {
      logger.error('Booking getCalendarRange error', { error });
      res.status(500).json({ success: false, message: 'Buchungen konnten nicht geladen werden' });
    }
  }

  /**
   * GET /api/bookings/upcoming?limit=
   * Returns the next upcoming non-cancelled bookings.
   */
  async getUpcoming(req: Request, res: Response): Promise<void> {
    try {
      const limit = parseInt(req.query.limit as string) || 20;
      const bookings = await bookingService.getUpcoming(limit);
      res.json({ success: true, data: bookings });
    } catch (error) {
      logger.error('Booking getUpcoming error', { error });
      res.status(500).json({ success: false, message: 'Buchungen konnten nicht geladen werden' });
    }
  }

  /**
   * GET /api/bookings/availability?fahrzeugId=&from=&to=
   * Returns { available: true } when the vehicle has no conflicting booking.
   */
  async checkAvailability(req: Request, res: Response): Promise<void> {
    try {
      const { fahrzeugId, from, to, excludeId } = req.query;
      if (!fahrzeugId || !from || !to) {
        res
          .status(400)
          .json({ success: false, message: 'fahrzeugId, from und to sind erforderlich' });
        return;
      }
      const beginn = new Date(from as string);
      const ende   = new Date(to as string);

      const outOfService = await bookingService.checkOutOfServiceConflict(
        fahrzeugId as string, beginn, ende
      );
      if (outOfService) {
        res.json({
          success: true,
          data: {
            available: false,
            reason: 'out_of_service',
            ausserDienstVon: outOfService.ausser_dienst_von.toISOString(),
            ausserDienstBis: outOfService.ausser_dienst_bis.toISOString(),
          },
        });
        return;
      }

      const hasConflict = await bookingService.checkConflict(
        fahrzeugId as string, beginn, ende, excludeId as string | undefined
      );
      res.json({ success: true, data: { available: !hasConflict } });
    } catch (error) {
      logger.error('Booking checkAvailability error', { error });
      res.status(500).json({ success: false, message: 'Verfügbarkeit konnte nicht geprüft werden' });
    }
  }

  /**
   * GET /api/bookings/:id
   * Returns a single booking with all joined fields.
   */
  async getById(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params as Record<string, string>;
      if (!isValidUUID(id)) {
        res.status(400).json({ success: false, message: 'Ungültige Buchungs-ID' });
        return;
      }
      const booking = await bookingService.getById(id);
      if (!booking) {
        res.status(404).json({ success: false, message: 'Buchung nicht gefunden' });
        return;
      }
      res.json({ success: true, data: booking });
    } catch (error) {
      logger.error('Booking getById error', { error, id: req.params.id });
      res.status(500).json({ success: false, message: 'Buchung konnte nicht geladen werden' });
    }
  }

  /**
   * POST /api/bookings
   * Creates a new vehicle booking.
   */
  async create(req: Request, res: Response): Promise<void> {
    try {
      const parsed = CreateBuchungSchema.safeParse(req.body);
      if (!parsed.success) {
        handleZodError(res, parsed.error);
        return;
      }
      const booking = await bookingService.create(parsed.data, req.user!.id, req.body.ignoreOutOfService === true);
      res.status(201).json({ success: true, data: booking });
    } catch (error: any) {
      if (handleConflictError(res, error)) return;
      logger.error('Booking create error', { error });
      res.status(500).json({ success: false, message: 'Buchung konnte nicht erstellt werden' });
    }
  }

  /**
   * PATCH /api/bookings/:id
   * Updates the provided fields of an existing booking.
   */
  async update(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params as Record<string, string>;
      if (!isValidUUID(id)) {
        res.status(400).json({ success: false, message: 'Ungültige Buchungs-ID' });
        return;
      }
      const parsed = UpdateBuchungSchema.safeParse(req.body);
      if (!parsed.success) {
        handleZodError(res, parsed.error);
        return;
      }
      if (Object.keys(parsed.data).length === 0) {
        res.status(400).json({ success: false, message: 'Kein Feld zum Aktualisieren angegeben' });
        return;
      }
      const booking = await bookingService.update(id, parsed.data);
      if (!booking) {
        res.status(404).json({ success: false, message: 'Buchung nicht gefunden' });
        return;
      }
      res.json({ success: true, data: booking });
    } catch (error: any) {
      if (error?.message === 'No fields to update') {
        res.status(400).json({ success: false, message: 'Kein Feld zum Aktualisieren angegeben' });
        return;
      }
      if (handleConflictError(res, error)) return;
      logger.error('Booking update error', { error, id: req.params.id });
      res.status(500).json({ success: false, message: 'Buchung konnte nicht aktualisiert werden' });
    }
  }

  /**
   * DELETE /api/bookings/:id  or  PATCH /api/bookings/:id/cancel
   * Soft-cancels a booking (sets abgesagt=TRUE).
   * Allowed for booking creator or users with bookings:write permission.
   */
  async cancel(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params as Record<string, string>;
      if (!isValidUUID(id)) {
        res.status(400).json({ success: false, message: 'Ungültige Buchungs-ID' });
        return;
      }

      // Check ownership: creator can cancel if they have cancel_own_bookings permission
      const booking = await bookingService.getById(id);
      if (!booking) {
        res.status(404).json({ success: false, message: 'Buchung nicht gefunden' });
        return;
      }
      const isOwner = booking.gebucht_von === req.user!.id;
      const groups: string[] = req.user?.groups ?? [];
      const isAdmin = groups.includes('dashboard_admin');
      const canCancelOwn = isAdmin || permissionService.hasPermission(groups, 'fahrzeugbuchungen:manage');
      const canCancelAny = isAdmin || permissionService.hasPermission(groups, 'fahrzeugbuchungen:manage');

      if (!(isOwner && canCancelOwn) && !canCancelAny) {
        res.status(403).json({ success: false, message: 'Keine Berechtigung' });
        return;
      }

      const parsed = CancelBuchungSchema.safeParse(req.body);
      if (!parsed.success) {
        handleZodError(res, parsed.error);
        return;
      }
      await bookingService.cancel(id, parsed.data.abgesagt_grund);
      res.json({ success: true, message: 'Buchung wurde storniert' });
    } catch (error) {
      logger.error('Booking cancel error', { error, id: req.params.id });
      res.status(500).json({ success: false, message: 'Buchung konnte nicht storniert werden' });
    }
  }

  /**
   * DELETE /api/bookings/:id/force
   * Hard-deletes a booking record (admin only).
   */
  async hardDelete(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params as Record<string, string>;
      if (!isValidUUID(id)) {
        res.status(400).json({ success: false, message: 'Ungültige Buchungs-ID' });
        return;
      }
      await bookingService.delete(id);
      res.json({ success: true, message: 'Buchung gelöscht' });
    } catch (error) {
      logger.error('Booking hardDelete error', { error, id: req.params.id });
      res.status(500).json({ success: false, message: 'Buchung konnte nicht gelöscht werden' });
    }
  }

  /**
   * GET /api/bookings/calendar-token
   * Returns the user's iCal subscribe token and URL, creating it if needed.
   */
  async getCalendarToken(req: Request, res: Response): Promise<void> {
    try {
      const result = await bookingService.getOrCreateIcalToken(req.user!.id);
      res.json({ success: true, data: result });
    } catch (error) {
      logger.error('Booking getCalendarToken error', { error });
      res.status(500).json({ success: false, message: 'Kalender-Token konnte nicht geladen werden' });
    }
  }

  /**
   * GET /api/bookings/calendar.ics?token=&fahrzeugId=
   * Returns an iCal file for the subscriber. No authentication required
   * (token-based access).
   */
  async getIcalExport(req: Request, res: Response): Promise<void> {
    try {
      const { token, fahrzeugId } = req.query;
      if (!token) {
        res.status(400).send('Token required');
        return;
      }
      const ical = await bookingService.getIcalExport(
        token as string,
        fahrzeugId as string | undefined
      );
      if (!ical) {
        res.status(404).send('Invalid token');
        return;
      }
      res.setHeader('Content-Type', 'text/calendar; charset=utf-8');
      res.setHeader('Content-Disposition', 'attachment; filename="fahrzeugbuchungen.ics"');
      res.send(ical);
    } catch (error) {
      logger.error('Booking getIcalExport error', { error });
      res.status(500).send('Internal server error');
    }
  }
}

export default new BookingController();
