/**
 * N8N Availability Check API
 *
 * Called by N8N workflows to check booking availability:
 * - Get available time slots for a date
 * - Check if a specific slot is available
 * - Get business hours
 *
 * Used by AI agents to offer appointment times.
 */

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { logger } from "@/lib/logger";
import { systemSettingsService } from "@/lib/services/system-settings.service";
import { z } from "zod";

// Request validation schema
const availabilityRequestSchema = z.object({
  auth_token: z.string().min(1),
  user_id: z.string().min(1),
  date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be YYYY-MM-DD"),
  service: z.string().optional(), // Optional service filter
  staff_member: z.string().optional(), // Optional staff filter
  duration: z.number().min(15).max(480).default(30), // Appointment duration in minutes
  check_slot: z
    .object({
      time: z.string().regex(/^\d{2}:\d{2}$/, "Time must be HH:MM"),
    })
    .optional(),
});

interface TimeSlot {
  time: string;
  available: boolean;
  staff_member?: string;
}

interface AvailabilityResponse {
  success: boolean;
  date?: string;
  business_hours?: {
    open: string;
    close: string;
    is_open: boolean;
  };
  available_slots?: TimeSlot[];
  slot_check?: {
    time: string;
    available: boolean;
    reason?: string;
  };
  error?: string;
}

// Default business hours (can be overridden by user preferences)
const DEFAULT_BUSINESS_HOURS = {
  sunday: { open: "09:00", close: "17:00", isOpen: true },
  monday: { open: "09:00", close: "17:00", isOpen: true },
  tuesday: { open: "09:00", close: "17:00", isOpen: true },
  wednesday: { open: "09:00", close: "17:00", isOpen: true },
  thursday: { open: "09:00", close: "17:00", isOpen: true },
  friday: { open: "09:00", close: "13:00", isOpen: true }, // Friday short hours (GCC)
  saturday: { open: "10:00", close: "14:00", isOpen: true },
};

export async function POST(request: NextRequest) {
  try {
    // Parse and validate request body
    const body = await request.json();
    const validationResult = availabilityRequestSchema.safeParse(body);

    if (!validationResult.success) {
      return NextResponse.json<AvailabilityResponse>(
        {
          success: false,
          error: "Invalid request: " + validationResult.error.errors[0].message,
        },
        { status: 400 },
      );
    }

    const {
      auth_token,
      user_id,
      date,
      service,
      staff_member,
      duration,
      check_slot,
    } = validationResult.data;

    // Validate auth token (from database or env fallback)
    const isValidToken =
      await systemSettingsService.validateN8NToken(auth_token);
    if (!isValidToken) {
      logger.warn("N8N availability check - invalid auth token", { user_id });
      return NextResponse.json<AvailabilityResponse>(
        { success: false, error: "Invalid authentication token" },
        { status: 401 },
      );
    }

    // Verify user exists
    const user = await prisma.users.findUnique({
      where: { id: user_id },
      include: {
        user_preferences: true,
      },
    });

    if (!user || !user.isActive) {
      return NextResponse.json<AvailabilityResponse>(
        { success: false, error: "User not found or inactive" },
        { status: 404 },
      );
    }

    // Parse date
    const [year, month, day] = date.split("-").map(Number);
    const targetDate = new Date(year, month - 1, day);
    const dayOfWeek = getDayOfWeek(targetDate);

    // Get business hours from user preferences or use defaults
    const businessHours = getBusinessHours(user.user_preferences, dayOfWeek);

    // If checking a specific slot
    if (check_slot) {
      const slotCheck = await checkSpecificSlot(
        user_id,
        targetDate,
        check_slot.time,
        duration,
        businessHours,
        staff_member,
      );

      return NextResponse.json<AvailabilityResponse>({
        success: true,
        date,
        business_hours: {
          open: businessHours.open,
          close: businessHours.close,
          is_open: businessHours.isOpen,
        },
        slot_check: slotCheck,
      });
    }

    // Get all available slots for the day
    const availableSlots = await getAvailableSlots(
      user_id,
      targetDate,
      duration,
      businessHours,
      service,
      staff_member,
    );

    logger.info("N8N availability check completed", {
      user_id,
      date,
      slots_available: availableSlots.filter((s) => s.available).length,
    });

    return NextResponse.json<AvailabilityResponse>({
      success: true,
      date,
      business_hours: {
        open: businessHours.open,
        close: businessHours.close,
        is_open: businessHours.isOpen,
      },
      available_slots: availableSlots,
    });
  } catch (error) {
    logger.error("N8N availability check error", { error });
    return NextResponse.json<AvailabilityResponse>(
      { success: false, error: "Internal server error" },
      { status: 500 },
    );
  }
}

/**
 * Get day of week name from date
 */
function getDayOfWeek(date: Date): string {
  const days = [
    "sunday",
    "monday",
    "tuesday",
    "wednesday",
    "thursday",
    "friday",
    "saturday",
  ];
  return days[date.getDay()];
}

/**
 * Get business hours for a specific day
 */
function getBusinessHours(
  preferences: { businessHours?: unknown } | null,
  dayOfWeek: string,
): { open: string; close: string; isOpen: boolean } {
  // Try to get from user preferences
  if (preferences?.businessHours) {
    const hours = preferences.businessHours as Record<
      string,
      { open: string; close: string; isOpen: boolean }
    >;
    if (hours[dayOfWeek]) {
      return hours[dayOfWeek];
    }
  }

  // Fall back to defaults
  return (
    DEFAULT_BUSINESS_HOURS[dayOfWeek as keyof typeof DEFAULT_BUSINESS_HOURS] ||
    DEFAULT_BUSINESS_HOURS.monday
  );
}

/**
 * Check if a specific time slot is available
 */
async function checkSpecificSlot(
  userId: string,
  date: Date,
  time: string,
  duration: number,
  businessHours: { open: string; close: string; isOpen: boolean },
  staffMember?: string,
): Promise<{ time: string; available: boolean; reason?: string }> {
  // Check if business is open
  if (!businessHours.isOpen) {
    return { time, available: false, reason: "Business closed on this day" };
  }

  // Check if time is within business hours
  if (time < businessHours.open) {
    return {
      time,
      available: false,
      reason: `Before opening hours (${businessHours.open})`,
    };
  }

  // Calculate end time
  const [hours, minutes] = time.split(":").map(Number);
  const endMinutes = hours * 60 + minutes + duration;
  const endTime = `${String(Math.floor(endMinutes / 60)).padStart(2, "0")}:${String(endMinutes % 60).padStart(2, "0")}`;

  if (endTime > businessHours.close) {
    return {
      time,
      available: false,
      reason: `Appointment would end after closing (${businessHours.close})`,
    };
  }

  // Check for conflicting bookings
  const startOfDay = new Date(date);
  startOfDay.setHours(0, 0, 0, 0);
  const endOfDay = new Date(date);
  endOfDay.setHours(23, 59, 59, 999);

  const conflictingBookings = await prisma.bookings.findMany({
    where: {
      userId,
      appointmentDate: { gte: startOfDay, lte: endOfDay },
      status: { in: ["pending", "confirmed"] },
      ...(staffMember ? { staffMember } : {}),
    },
    select: {
      appointmentTime: true,
      duration: true,
    },
  });

  // Check for overlaps
  const requestedStart = hours * 60 + minutes;
  const requestedEnd = requestedStart + duration;

  for (const booking of conflictingBookings) {
    const [bHours, bMinutes] = booking.appointmentTime.split(":").map(Number);
    const bookingStart = bHours * 60 + bMinutes;
    const bookingEnd = bookingStart + (booking.duration || 30);

    // Check for overlap
    if (requestedStart < bookingEnd && requestedEnd > bookingStart) {
      return { time, available: false, reason: "Time slot already booked" };
    }
  }

  return { time, available: true };
}

/**
 * Get all available slots for a day
 */
async function getAvailableSlots(
  userId: string,
  date: Date,
  duration: number,
  businessHours: { open: string; close: string; isOpen: boolean },
  service?: string,
  staffMember?: string,
): Promise<TimeSlot[]> {
  const slots: TimeSlot[] = [];

  if (!businessHours.isOpen) {
    return slots;
  }

  // Generate time slots (every 30 minutes)
  const [openHour, openMin] = businessHours.open.split(":").map(Number);
  const [closeHour, closeMin] = businessHours.close.split(":").map(Number);

  const openMinutes = openHour * 60 + openMin;
  const closeMinutes = closeHour * 60 + closeMin;

  // Get existing bookings for the day
  const startOfDay = new Date(date);
  startOfDay.setHours(0, 0, 0, 0);
  const endOfDay = new Date(date);
  endOfDay.setHours(23, 59, 59, 999);

  const existingBookings = await prisma.bookings.findMany({
    where: {
      userId,
      appointmentDate: { gte: startOfDay, lte: endOfDay },
      status: { in: ["pending", "confirmed"] },
      ...(service ? { service } : {}),
      ...(staffMember ? { staffMember } : {}),
    },
    select: {
      appointmentTime: true,
      duration: true,
      staffMember: true,
    },
  });

  // Convert bookings to minute ranges for easy conflict checking
  const bookedRanges = existingBookings.map((b) => {
    const [h, m] = b.appointmentTime.split(":").map(Number);
    const start = h * 60 + m;
    return {
      start,
      end: start + (b.duration || 30),
      staffMember: b.staffMember,
    };
  });

  // Generate slots every 30 minutes
  for (let time = openMinutes; time + duration <= closeMinutes; time += 30) {
    const timeStr = `${String(Math.floor(time / 60)).padStart(2, "0")}:${String(time % 60).padStart(2, "0")}`;
    const slotEnd = time + duration;

    // Check if slot conflicts with any booking
    const conflict = bookedRanges.find(
      (range) => time < range.end && slotEnd > range.start,
    );

    slots.push({
      time: timeStr,
      available: !conflict,
      staff_member: conflict?.staffMember,
    });
  }

  return slots;
}

// Only allow POST method
export async function GET() {
  return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
}
