/**
 * Voice Agent Server Tool - Viewing Availability Check
 *
 * POST /api/voice-agent/server-tools/viewing-availability
 * Called by ElevenLabs during live conversations to check available viewing slots
 * Auth: token-based via ?token= query param
 */

export const dynamic = "force-dynamic";

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { logger } from "@/lib/logger";
import { getEffectiveOwnerId } from "@/lib/utils/organization-data";
import { getDataSource } from "@/lib/data-source";

const scopedLogger = logger.scope("VoiceAgentViewingAvailabilityTool");

/**
 * Authenticate via token query parameter
 */
async function authenticateToken(request: NextRequest): Promise<string | null> {
  const token = request.nextUrl.searchParams.get("token");
  if (!token) return null;

  // ── Convex data-source branch ──────────────────────────────────────
  if (getDataSource("voice_agents") === "convex") {
    try {
      const { convexQuery } = await import("@/lib/convex-client");
      const cvxUser = (await convexQuery(
        "voice_agents/queries:getUserByWebhookToken",
        {
          token,
        },
      )) as { prismaId?: string; _id: string } | null;
      return cvxUser?.prismaId ?? cvxUser?._id ?? null;
    } catch (convexError) {
      console.error(
        "[Convex fallback] voice_agents viewing-availability auth:",
        convexError,
      );
    }
  }

  const user = await prisma.users.findFirst({
    where: { n8nWebhookToken: token },
    select: { id: true },
  });

  return user?.id || null;
}

/**
 * Get the next N business days (Sun-Thu for GCC region) from a given date.
 * In GCC, the work week is Sunday through Thursday.
 */
function getNextBusinessDays(from: Date, count: number): Date[] {
  const days: Date[] = [];
  const current = new Date(from);
  current.setHours(0, 0, 0, 0);

  while (days.length < count) {
    const dayOfWeek = current.getDay();
    // GCC business days: 0=Sun, 1=Mon, 2=Tue, 3=Wed, 4=Thu
    if (dayOfWeek >= 0 && dayOfWeek <= 4) {
      days.push(new Date(current));
    }
    current.setDate(current.getDate() + 1);
  }

  return days;
}

/**
 * Generate 30-minute time slots between start and end hours
 */
function generateTimeSlots(startHour: number, endHour: number): string[] {
  const slots: string[] = [];
  for (let hour = startHour; hour < endHour; hour++) {
    for (const minute of ["00", "30"]) {
      slots.push(`${hour.toString().padStart(2, "0")}:${minute}`);
    }
  }
  return slots;
}

export async function POST(request: NextRequest) {
  try {
    const userId = await authenticateToken(request);
    if (!userId) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    const effectiveUserId = await getEffectiveOwnerId(userId);

    const body = await request.json();
    const propertyId = body.property_id || body.parameters?.property_id;
    const preferredDate =
      body.preferred_date || body.parameters?.preferred_date;
    const preferredLanguage =
      body.preferred_language || body.parameters?.preferred_language;

    if (!propertyId) {
      return NextResponse.json({
        success: false,
        message:
          "Please specify which property you would like to check viewing availability for.",
      });
    }

    scopedLogger.info("Viewing availability check", {
      userId,
      effectiveUserId,
      propertyId,
      preferredDate,
      preferredLanguage,
    });

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

    if (!property) {
      return NextResponse.json({
        success: false,
        message:
          "I could not find that property. Could you please provide the correct property reference?",
      });
    }

    if (property.status !== "AVAILABLE") {
      return NextResponse.json({
        success: true,
        message: `${property.titleEn} is currently ${property.status.toLowerCase().replace(/_/g, " ")} and not available for viewings.`,
        available_slots: [],
      });
    }

    // Determine which dates to check
    let datesToCheck: Date[];
    if (preferredDate) {
      const requestedDate = new Date(preferredDate);
      requestedDate.setHours(0, 0, 0, 0);
      datesToCheck = [requestedDate];
    } else {
      // Check next 3 business days (GCC: Sun-Thu)
      const tomorrow = new Date();
      tomorrow.setDate(tomorrow.getDate() + 1);
      datesToCheck = getNextBusinessDays(tomorrow, 3);
    }

    // Find active brokers for this user
    let brokers = await prisma.broker.findMany({
      where: { userId: effectiveUserId, isActive: true },
      select: {
        id: true,
        nameEn: true,
        languages: true,
        workStartTime: true,
        workEndTime: true,
        workingDays: true,
        maxViewingsPerDay: true,
      },
    });

    // Filter by language preference if specified
    if (preferredLanguage && brokers.length > 0) {
      const languageFilteredBrokers = brokers.filter((broker) => {
        try {
          const langs = Array.isArray(broker.languages)
            ? broker.languages
            : typeof broker.languages === "string"
              ? JSON.parse(broker.languages)
              : [];
          return langs.some(
            (l: string) => l.toLowerCase() === preferredLanguage.toLowerCase(),
          );
        } catch {
          return false;
        }
      });

      // Only apply language filter if it yields results
      if (languageFilteredBrokers.length > 0) {
        brokers = languageFilteredBrokers;
      }
    }

    // For each date, check existing viewings and generate available slots
    const availabilityByDate: Array<{
      date: string;
      dayName: string;
      slots: string[];
      brokerCount: number;
    }> = [];

    for (const checkDate of datesToCheck) {
      const nextDay = new Date(checkDate);
      nextDay.setDate(nextDay.getDate() + 1);

      // Get existing viewings for this property on this date
      const existingViewings = await prisma.propertyViewing.findMany({
        where: {
          propertyId,
          viewingDate: {
            gte: checkDate,
            lt: nextDay,
          },
          status: {
            notIn: ["CANCELLED", "NO_SHOW"],
          },
        },
        select: {
          viewingTime: true,
          brokerId: true,
        },
      });

      const bookedTimes = new Set(existingViewings.map((v) => v.viewingTime));

      // Determine business hours (use first broker's schedule as default)
      const startHour =
        brokers.length > 0
          ? parseInt(brokers[0].workStartTime.split(":")[0], 10)
          : 9;
      const endHour =
        brokers.length > 0
          ? parseInt(brokers[0].workEndTime.split(":")[0], 10)
          : 18;

      // Generate available slots (excluding already-booked times)
      const allSlots = generateTimeSlots(startHour, endHour);
      const availableSlots = allSlots.filter((slot) => !bookedTimes.has(slot));

      const dateStr = checkDate.toISOString().split("T")[0];
      const dayName = checkDate.toLocaleDateString("en-US", {
        weekday: "long",
      });

      availabilityByDate.push({
        date: dateStr,
        dayName,
        slots: availableSlots,
        brokerCount: brokers.length,
      });
    }

    // Build natural language response
    const hasSlots = availabilityByDate.some((d) => d.slots.length > 0);

    if (!hasSlots) {
      const dateRangeText =
        datesToCheck.length === 1
          ? datesToCheck[0].toISOString().split("T")[0]
          : `the next ${datesToCheck.length} business days`;

      return NextResponse.json({
        success: true,
        message: `Sorry, there are no viewing slots available for ${property.titleEn} on ${dateRangeText}. Would you like to try different dates?`,
        available_slots: [],
        availability: availabilityByDate,
      });
    }

    const parts: string[] = [];
    for (const day of availabilityByDate) {
      if (day.slots.length > 0) {
        const sampleSlots = day.slots.slice(0, 6).join(", ");
        const moreText =
          day.slots.length > 6 ? ` and ${day.slots.length - 6} more` : "";
        parts.push(
          `On ${day.dayName} ${day.date}, available slots are: ${sampleSlots}${moreText}`,
        );
      }
    }

    const message = `For ${property.titleEn}: ${parts.join(". ")}. Would you like to book one of these slots?`;

    return NextResponse.json({
      success: true,
      message,
      availability: availabilityByDate,
      property_id: property.id,
      property_title: property.titleEn,
    });
  } catch (error) {
    scopedLogger.error("Error checking viewing availability", error);
    return NextResponse.json(
      {
        success: false,
        message:
          "Sorry, I could not check viewing availability right now. Please try again.",
      },
      { status: 500 },
    );
  }
}
