import { Request, Response } from 'express';
import Appointments from '../../appointments/schema/appointments.schema';
import { AppointmentStatus } from '../../appointments/enums/appointments.enum';
import { generateSlots, toDateKey } from '../../../helpers/availability.helper';
import { BlockedDay, FulfilledDay, WeeklySchedule } from '../schema/availability.schema';

const DEFAULT_SCHEDULE = [
  { dayOfWeek: 0, isOpen: false, openTime: '09:00', closeTime: '18:00', slotIntervalMinutes: 60 },
  { dayOfWeek: 1, isOpen: true, openTime: '09:00', closeTime: '18:00', slotIntervalMinutes: 60 },
  { dayOfWeek: 2, isOpen: true, openTime: '09:00', closeTime: '18:00', slotIntervalMinutes: 60 },
  { dayOfWeek: 3, isOpen: true, openTime: '09:00', closeTime: '18:00', slotIntervalMinutes: 60 },
  { dayOfWeek: 4, isOpen: true, openTime: '09:00', closeTime: '18:00', slotIntervalMinutes: 60 },
  { dayOfWeek: 5, isOpen: true, openTime: '09:00', closeTime: '18:00', slotIntervalMinutes: 60 },
  { dayOfWeek: 6, isOpen: true, openTime: '09:00', closeTime: '18:00', slotIntervalMinutes: 60 },
];

async function ensureSchedule() {
  const count = await WeeklySchedule.countDocuments();
  if (count === 0) {
    await WeeklySchedule.insertMany(DEFAULT_SCHEDULE);
  }
  return WeeklySchedule.find().sort({ dayOfWeek: 1 });
}

async function isDateBookable(dateKey: string) {
  const blocked = await BlockedDay.findOne({ date: dateKey });
  if (blocked) return { bookable: false, reason: blocked.reason || 'Closed' };

  const fulfilled = await FulfilledDay.findOne({ date: dateKey });
  if (fulfilled) return { bookable: false, reason: fulfilled.reason || 'Fully booked' };

  const date = new Date(`${dateKey}T12:00:00`);
  const schedule = await WeeklySchedule.findOne({ dayOfWeek: date.getDay() });
  if (!schedule || !schedule.isOpen) {
    return { bookable: false, reason: 'Closed' };
  }

  return { bookable: true, schedule };
}

export class AvailabilityController {
  static async getSchedule(req: Request, res: Response) {
    try {
      const response = await ensureSchedule();
      return res.status(200).json({ success: true, response });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async updateSchedule(req: Request, res: Response) {
    try {
      const { schedule } = req.body;
      if (!Array.isArray(schedule)) {
        return res.status(400).json({ success: false, message: 'Schedule array required' });
      }

      await WeeklySchedule.deleteMany({});
      await WeeklySchedule.insertMany(schedule);
      const response = await WeeklySchedule.find().sort({ dayOfWeek: 1 });
      return res.status(200).json({ success: true, response });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async listBlocked(req: Request, res: Response) {
    try {
      const response = await BlockedDay.find().sort({ date: 1 });
      return res.status(200).json({ success: true, response });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async createBlocked(req: Request, res: Response) {
    try {
      const { date, reason } = req.body;
      if (!date) return res.status(400).json({ success: false, message: 'Date required' });
      const response = await BlockedDay.create({ date, reason });
      return res.status(201).json({ success: true, response });
    } catch (e: any) {
      if (e?.code === 11000) {
        return res.status(409).json({ success: false, message: 'Date already blocked' });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async deleteBlocked(req: Request, res: Response) {
    try {
      await BlockedDay.findByIdAndDelete(req.params.id);
      return res.status(200).json({ success: true, message: 'Removed' });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async listFulfilled(req: Request, res: Response) {
    try {
      const response = await FulfilledDay.find().sort({ date: 1 });
      return res.status(200).json({ success: true, response });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async createFulfilled(req: Request, res: Response) {
    try {
      const { date, reason } = req.body;
      if (!date) return res.status(400).json({ success: false, message: 'Date required' });
      const response = await FulfilledDay.create({ date, reason });
      return res.status(201).json({ success: true, response });
    } catch (e: any) {
      if (e?.code === 11000) {
        return res.status(409).json({ success: false, message: 'Date already marked fulfilled' });
      }
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async deleteFulfilled(req: Request, res: Response) {
    try {
      await FulfilledDay.findByIdAndDelete(req.params.id);
      return res.status(200).json({ success: true, message: 'Removed' });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async getAvailableDates(req: Request, res: Response) {
    try {
      const days = Math.min(parseInt(String(req.query.days || '60'), 10), 90);
      await ensureSchedule();
      const blocked = new Set((await BlockedDay.find()).map((d: any) => d.date));
      const fulfilled = new Set((await FulfilledDay.find()).map((d: any) => d.date));
      const schedules = await WeeklySchedule.find();
      const scheduleMap = new Map(schedules.map((s: any) => [s.dayOfWeek, s]));

      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const dates: string[] = [];

      for (let i = 0; i < days; i += 1) {
        const date = new Date(today);
        date.setDate(today.getDate() + i);
        const key = toDateKey(date);
        if (blocked.has(key) || fulfilled.has(key)) continue;
        const schedule: any = scheduleMap.get(date.getDay());
        if (schedule?.isOpen) dates.push(key);
      }

      return res.status(200).json({ success: true, response: dates });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }

  static async getAvailableSlots(req: Request, res: Response) {
    try {
      const dateKey = String(req.query.date || '');
      if (!dateKey) {
        return res.status(400).json({ success: false, message: 'Date query required' });
      }

      const check = await isDateBookable(dateKey);
      if (!check.bookable) {
        return res.status(200).json({ success: true, response: [], reason: check.reason });
      }

      const schedule = check.schedule;
      const allSlots = generateSlots(
        schedule.openTime,
        schedule.closeTime,
        schedule.slotIntervalMinutes || 60
      );

      const booked = await Appointments.find({
        date: dateKey,
        status: { $nin: [AppointmentStatus.CANCELLED] },
      }).select('time');

      const bookedSet = new Set(booked.map((a: any) => a.time));
      const response = allSlots.filter((slot) => !bookedSet.has(slot));

      return res.status(200).json({ success: true, response });
    } catch {
      return res.status(500).json({ success: false, message: 'System error' });
    }
  }
}
