/**
 * Viewing Availability API
 * GET - Available 30-min time slots for a property on a given date
 *
 * Returns open slots between 09:00-18:00 (or broker working hours),
 * excluding booked slots and broker availability blocks.
 *
 * Supports both NextAuth and OTP (auth_token) authentication
 */

import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/db";
import { getAuthenticatedEmail } from "@/lib/utils/auth-helpers.util";
import { getEffectiveOwnerId } from "@/lib/utils/organization-data";
import { logger } from "@/lib/logger";
import { ApiResponses } from "@/lib/utils/api-response.util";
import { withRateLimit, rateLimiters } from "@/lib/rate-limiter";
import { getErrorMessage } from "@/lib/errors";
import { getDataSource } from "@/lib/data-source";

export const dynamic = "force-dynamic";

/** Default slot duration in minutes */
const SLOT_DURATION_MINUTES = 30;

/** Default working hours */
const DEFAULT_WORK_START = "09:00";
const DEFAULT_WORK_END = "18:00";

/**
 * Convert HH:MM to minutes since midnight
 */
function timeToMinutes(time: string): number {
  const [hours, minutes] = time.split(":").map(Number);
  return hours * 60 + minutes;
}

/**
 * Convert minutes since midnight to HH:MM
 */
function minutesToTime(minutes: number): string {
  const h = Math.floor(minutes / 60);
  const m = minutes % 60;
  return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}

/**
 * GET /api/real-estate/viewings/availability
 * Returns available 30-minute time slots for a given date.
 *
 * Query params:
 *   - propertyId (required): property to check availability for
 *   - date (required): YYYY-MM-DD
 *   - brokerId (optional): filter by broker working hours and existing bookings
 */
export const GET = withRateLimit(async (request: NextRequest) => {
  try {
    const userEmail = await getAuthenticatedEmail(request, auth);

    if (!userEmail) {
      logger.warn("Unauthorized availability request");
      return ApiResponses.unauthorized();
    }

    const user = await prisma.users.findUnique({
      where: { email: userEmail },
      select: { id: true },
    });

    if (!user) {
      return ApiResponses.notFound("User not found");
    }

    const effectiveUserId = await getEffectiveOwnerId(user.id);

    // Convex branch
    if (getDataSource("real_estate") === "convex") {
      try {
        const searchParams = request.nextUrl.searchParams;
        const propertyId = searchParams.get("propertyId");
        const date = searchParams.get("date");
        const brokerId = searchParams.get("brokerId");

        if (!propertyId) {
          return ApiResponses.badRequest(
            "propertyId query parameter is required",
          );
        }
        if (!date) {
          return ApiResponses.badRequest("date query parameter is required");
        }

        const { convexQuery } = await import("@/lib/convex-client");
        const parsedDate = new Date(date);
        const result = await convexQuery(
          "real_estate/queries:getViewingAvailability",
          {
            userId: effectiveUserId,
            propertyId,
            date: parsedDate.getTime(),
            ...(brokerId ? { brokerId } : {}),
          },
        );
        if (result !== null) {
          return NextResponse.json({
            success: true,
            date,
            propertyId,
            brokerId: brokerId || null,
            brokerName: null,
            slotDuration: 30,
            workingHours: { start: "09:00", end: "18:00" },
            slots: [],
            availableCount: 0,
            totalSlots: 0,
          });
        }
      } catch (error) {
        console.error("[Convex fallback] real_estate availability GET:", error);
      }
    }

    const searchParams = request.nextUrl.searchParams;
    const propertyId = searchParams.get("propertyId");
    const date = searchParams.get("date");
    const brokerId = searchParams.get("brokerId");

    // --- Validation ---
    if (!propertyId) {
      return ApiResponses.badRequest("propertyId query parameter is required");
    }

    if (!date) {
      return ApiResponses.badRequest("date query parameter is required");
    }

    const parsedDate = new Date(date);
    if (isNaN(parsedDate.getTime())) {
      return ApiResponses.badRequest("Invalid date format. Use YYYY-MM-DD");
    }

    // Don't check past dates
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    if (parsedDate < today) {
      return ApiResponses.badRequest(
        "Cannot check availability for past dates",
      );
    }

    // Verify property exists and belongs to user
    const property = await prisma.property.findFirst({
      where: {
        id: propertyId,
        userId: effectiveUserId,
      },
      select: { id: true, titleEn: true },
    });

    if (!property) {
      return ApiResponses.notFound("Property not found");
    }

    // --- Determine working hours ---
    let workStart = DEFAULT_WORK_START;
    let workEnd = DEFAULT_WORK_END;
    let brokerName: string | null = null;
    let maxViewingsPerDay = Infinity;

    if (brokerId) {
      const broker = await prisma.broker.findFirst({
        where: {
          id: brokerId,
          userId: effectiveUserId,
          isActive: true,
        },
        select: {
          id: true,
          nameEn: true,
          workingDays: true,
          workStartTime: true,
          workEndTime: true,
          maxViewingsPerDay: true,
        },
      });

      if (!broker) {
        return ApiResponses.notFound("Broker not found or inactive");
      }

      // Check if broker works on this day
      const dayOfWeek = parsedDate.getDay();
      const workingDays = broker.workingDays as number[];
      if (!workingDays.includes(dayOfWeek)) {
        return NextResponse.json({
          success: true,
          date,
          propertyId,
          brokerId,
          brokerName: broker.nameEn,
          available: false,
          reason: "Broker does not work on this day",
          slots: [],
        });
      }

      workStart = broker.workStartTime;
      workEnd = broker.workEndTime;
      brokerName = broker.nameEn;
      maxViewingsPerDay = broker.maxViewingsPerDay;
    }

    // --- Fetch existing viewings for this property on this date ---
    const propertyViewings = await prisma.propertyViewing.findMany({
      where: {
        propertyId,
        viewingDate: parsedDate,
        status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
      },
      select: {
        viewingTime: true,
        duration: true,
        viewingType: true,
      },
      orderBy: { viewingTime: "asc" },
    });

    // --- Fetch broker viewings if broker specified (across all properties) ---
    let brokerViewings: Array<{ viewingTime: string; duration: number }> = [];
    if (brokerId) {
      brokerViewings = await prisma.propertyViewing.findMany({
        where: {
          brokerId,
          viewingDate: parsedDate,
          status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
        },
        select: {
          viewingTime: true,
          duration: true,
        },
        orderBy: { viewingTime: "asc" },
      });

      // Check if broker has hit daily limit
      if (brokerViewings.length >= maxViewingsPerDay) {
        return NextResponse.json({
          success: true,
          date,
          propertyId,
          brokerId,
          brokerName,
          available: false,
          reason: "Broker has reached maximum viewings for this day",
          slots: [],
          existingCount: brokerViewings.length,
          maxPerDay: maxViewingsPerDay,
        });
      }
    }

    // --- Fetch broker availability blocks (blocked times) ---
    let blockedRanges: Array<{ start: number; end: number }> = [];
    if (brokerId) {
      const blocks = await prisma.brokerAvailability.findMany({
        where: {
          brokerId,
          date: parsedDate,
          isBlocked: true,
        },
        select: {
          startTime: true,
          endTime: true,
        },
      });

      blockedRanges = blocks.map((block) => ({
        start: timeToMinutes(block.startTime),
        end: timeToMinutes(block.endTime),
      }));
    }

    // --- Build occupied time ranges (in minutes since midnight) ---
    const occupiedRanges: Array<{ start: number; end: number }> = [];

    // Property viewings (non-OPEN_HOUSE block the slot)
    for (const viewing of propertyViewings) {
      if (viewing.viewingType === "OPEN_HOUSE") {
        continue; // Open house slots can be shared
      }
      const start = timeToMinutes(viewing.viewingTime);
      const end = start + (viewing.duration || SLOT_DURATION_MINUTES);
      occupiedRanges.push({ start, end });
    }

    // Broker viewings across all properties
    for (const viewing of brokerViewings) {
      const start = timeToMinutes(viewing.viewingTime);
      const end = start + (viewing.duration || SLOT_DURATION_MINUTES);
      occupiedRanges.push({ start, end });
    }

    // Add blocked ranges
    occupiedRanges.push(...blockedRanges);

    // --- Generate available slots ---
    const startMinutes = timeToMinutes(workStart);
    const endMinutes = timeToMinutes(workEnd);
    const slots: Array<{
      time: string;
      available: boolean;
      conflictReason?: string;
    }> = [];

    // If checking today, skip past time slots
    const now = new Date();
    let earliestMinutes = startMinutes;
    if (parsedDate.toDateString() === now.toDateString()) {
      const currentMinutes = now.getHours() * 60 + now.getMinutes();
      // Round up to next slot boundary + 30 min buffer
      earliestMinutes = Math.max(
        startMinutes,
        Math.ceil((currentMinutes + 30) / SLOT_DURATION_MINUTES) *
          SLOT_DURATION_MINUTES,
      );
    }

    for (
      let slotStart = earliestMinutes;
      slotStart + SLOT_DURATION_MINUTES <= endMinutes;
      slotStart += SLOT_DURATION_MINUTES
    ) {
      const slotEnd = slotStart + SLOT_DURATION_MINUTES;
      const time = minutesToTime(slotStart);

      // Check if this slot overlaps with any occupied range
      const conflict = occupiedRanges.find(
        (range) => slotStart < range.end && slotEnd > range.start,
      );

      if (conflict) {
        slots.push({
          time,
          available: false,
          conflictReason: "Time slot is already booked",
        });
      } else {
        slots.push({
          time,
          available: true,
        });
      }
    }

    const availableSlots = slots.filter((s) => s.available);

    logger.info("Availability checked", {
      userEmail,
      date,
      propertyId,
      brokerId,
      totalSlots: slots.length,
      availableSlots: availableSlots.length,
    });

    return NextResponse.json({
      success: true,
      date,
      propertyId,
      brokerId: brokerId || null,
      brokerName,
      slotDuration: SLOT_DURATION_MINUTES,
      workingHours: {
        start: workStart,
        end: workEnd,
      },
      slots,
      availableCount: availableSlots.length,
      totalSlots: slots.length,
    });
  } catch (error) {
    logger.error("Availability check error", {
      error: getErrorMessage(error),
    });
    return ApiResponses.serverError("Failed to check availability");
  }
}, rateLimiters.authGeneral);
