/**
 * Voice Agent Server Tool - Property Viewing Management
 *
 * POST /api/voice-agent/server-tools/viewing
 * Called by ElevenLabs during live conversations to book/cancel/reschedule viewings
 * 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 { createId } from "@paralleldrive/cuid2";
import { getEffectiveOwnerId } from "@/lib/utils/organization-data";
import { getDataSource } from "@/lib/data-source";

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

/**
 * 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 auth:",
        convexError,
      );
    }
  }

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

  return user?.id || null;
}

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 { action, agent_id, call_id, ...params } = body;

    // Also try to get action from parameters (ElevenLabs may nest it)
    const effectiveAction = action || params.parameters?.action || "book";

    scopedLogger.info("Viewing tool called", {
      userId,
      effectiveUserId,
      action: effectiveAction,
      call_id,
    });

    switch (effectiveAction) {
      case "book": {
        const propertyId = params.property_id || params.parameters?.property_id;
        const clientName = params.client_name || params.parameters?.client_name;
        const clientPhone =
          params.client_phone || params.parameters?.client_phone;
        const date = params.date || params.parameters?.date;
        const time = params.time || params.parameters?.time;
        const brokerLanguage =
          params.broker_language || params.parameters?.broker_language;
        const notes = params.notes || params.parameters?.notes;

        if (!propertyId || !clientName || !clientPhone || !date || !time) {
          return NextResponse.json({
            success: false,
            message:
              "I need the property, client name, phone number, date, and time to book a viewing. Could you please provide the missing details?",
          });
        }

        // Verify property exists and is available
        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. Please provide a valid property reference.",
          });
        }

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

        // Check for conflicting viewing at same property + date + time
        const viewingDate = new Date(date);
        viewingDate.setHours(0, 0, 0, 0);
        const nextDay = new Date(viewingDate);
        nextDay.setDate(nextDay.getDate() + 1);

        const existingViewing = await prisma.propertyViewing.findFirst({
          where: {
            propertyId,
            viewingDate: {
              gte: viewingDate,
              lt: nextDay,
            },
            viewingTime: time,
            status: {
              notIn: ["CANCELLED", "NO_SHOW"],
            },
          },
        });

        if (existingViewing) {
          return NextResponse.json({
            success: false,
            message: `There is already a viewing booked for ${property.titleEn} at ${time} on ${date}. Would you like to choose a different time?`,
          });
        }

        // Find the best available broker
        const brokers = await prisma.broker.findMany({
          where: { userId: effectiveUserId, isActive: true },
          select: {
            id: true,
            nameEn: true,
            languages: true,
          },
        });

        let selectedBroker: { id: string; nameEn: string } | null = null;

        if (brokers.length > 0) {
          // If language preference, try to match
          if (brokerLanguage) {
            const languageMatch = brokers.find((b) => {
              try {
                const langs = Array.isArray(b.languages)
                  ? b.languages
                  : typeof b.languages === "string"
                    ? JSON.parse(b.languages)
                    : [];
                return langs.some(
                  (l: string) =>
                    l.toLowerCase() === brokerLanguage.toLowerCase(),
                );
              } catch {
                return false;
              }
            });
            if (languageMatch) {
              selectedBroker = languageMatch;
            }
          }

          // If no language match or no preference, pick broker with fewest viewings that day
          if (!selectedBroker) {
            const brokerViewingCounts = await Promise.all(
              brokers.map(async (b) => {
                const count = await prisma.propertyViewing.count({
                  where: {
                    brokerId: b.id,
                    viewingDate: {
                      gte: viewingDate,
                      lt: nextDay,
                    },
                    status: {
                      notIn: ["CANCELLED", "NO_SHOW"],
                    },
                  },
                });
                return { broker: b, count };
              }),
            );

            // Sort by fewest viewings
            brokerViewingCounts.sort((a, b) => a.count - b.count);
            selectedBroker = brokerViewingCounts[0].broker;
          }
        }

        // Create the viewing
        const viewingId = createId();
        await prisma.propertyViewing.create({
          data: {
            id: viewingId,
            userId: effectiveUserId,
            propertyId,
            brokerId: selectedBroker?.id || null,
            clientName,
            clientPhone,
            viewingDate,
            viewingTime: time,
            status: "CONFIRMED",
            source: "voice",
            duration: 30,
            clientNotes: notes || null,
            conversationId: call_id || null,
          },
        });

        scopedLogger.info("Viewing booked via voice agent", {
          viewingId,
          propertyId,
          userId: effectiveUserId,
        });

        const brokerText = selectedBroker
          ? ` with ${selectedBroker.nameEn}`
          : "";

        return NextResponse.json({
          success: true,
          message: `Your viewing for ${property.titleEn} has been booked for ${date} at ${time}${brokerText}. You'll receive a confirmation message shortly. Viewing reference: ${viewingId.slice(-6).toUpperCase()}.`,
          viewing_id: viewingId,
        });
      }

      case "cancel": {
        const clientPhone =
          params.client_phone || params.parameters?.client_phone;
        const viewingDate =
          params.viewing_date || params.parameters?.viewing_date;
        const viewingId = params.viewing_id || params.parameters?.viewing_id;

        if (!clientPhone && !viewingId) {
          return NextResponse.json({
            success: false,
            message:
              "Please provide either a viewing reference or the phone number used for the booking to cancel.",
          });
        }

        let viewing;
        if (viewingId) {
          viewing = await prisma.propertyViewing.findFirst({
            where: {
              id: viewingId,
              userId: effectiveUserId,
              status: { not: "CANCELLED" },
            },
            include: {
              property: { select: { titleEn: true } },
            },
          });
        } else {
          const where: Record<string, unknown> = {
            userId: effectiveUserId,
            clientPhone,
            status: { notIn: ["CANCELLED", "NO_SHOW"] },
          };
          if (viewingDate) {
            const cancelDate = new Date(viewingDate);
            cancelDate.setHours(0, 0, 0, 0);
            const cancelNextDay = new Date(cancelDate);
            cancelNextDay.setDate(cancelNextDay.getDate() + 1);
            where.viewingDate = {
              gte: cancelDate,
              lt: cancelNextDay,
            };
          }
          viewing = await prisma.propertyViewing.findFirst({
            where,
            orderBy: { viewingDate: "asc" },
            include: {
              property: { select: { titleEn: true } },
            },
          });
        }

        if (!viewing) {
          return NextResponse.json({
            success: false,
            message:
              "No matching viewing found. Could you please confirm the phone number or viewing reference?",
          });
        }

        await prisma.propertyViewing.update({
          where: { id: viewing.id },
          data: { status: "CANCELLED" },
        });

        scopedLogger.info("Viewing cancelled via voice agent", {
          viewingId: viewing.id,
        });

        const cancelDateStr = viewing.viewingDate.toISOString().split("T")[0];

        return NextResponse.json({
          success: true,
          message: `Your viewing for ${viewing.property.titleEn} on ${cancelDateStr} at ${viewing.viewingTime} has been cancelled.`,
        });
      }

      case "reschedule": {
        const clientPhone =
          params.client_phone || params.parameters?.client_phone;
        const originalDate =
          params.original_date || params.parameters?.original_date;
        const newDate = params.new_date || params.parameters?.new_date;
        const newTime = params.new_time || params.parameters?.new_time;
        const viewingId = params.viewing_id || params.parameters?.viewing_id;

        if (!newDate || !newTime) {
          return NextResponse.json({
            success: false,
            message:
              "Please provide the new date and time to reschedule the viewing.",
          });
        }

        // Find existing viewing
        let viewing;
        if (viewingId) {
          viewing = await prisma.propertyViewing.findFirst({
            where: {
              id: viewingId,
              userId: effectiveUserId,
              status: { notIn: ["CANCELLED", "NO_SHOW"] },
            },
            include: {
              property: { select: { titleEn: true } },
            },
          });
        } else if (clientPhone) {
          const where: Record<string, unknown> = {
            userId: effectiveUserId,
            clientPhone,
            status: { notIn: ["CANCELLED", "NO_SHOW"] },
          };
          if (originalDate) {
            const origDate = new Date(originalDate);
            origDate.setHours(0, 0, 0, 0);
            const origNextDay = new Date(origDate);
            origNextDay.setDate(origNextDay.getDate() + 1);
            where.viewingDate = {
              gte: origDate,
              lt: origNextDay,
            };
          }
          viewing = await prisma.propertyViewing.findFirst({
            where,
            orderBy: { viewingDate: "asc" },
            include: {
              property: { select: { titleEn: true } },
            },
          });
        }

        if (!viewing) {
          return NextResponse.json({
            success: false,
            message:
              "No matching viewing found to reschedule. Could you confirm the viewing reference or phone number?",
          });
        }

        // Check new slot availability
        const newViewingDate = new Date(newDate);
        newViewingDate.setHours(0, 0, 0, 0);
        const newNextDay = new Date(newViewingDate);
        newNextDay.setDate(newNextDay.getDate() + 1);

        const conflicting = await prisma.propertyViewing.findFirst({
          where: {
            propertyId: viewing.propertyId,
            viewingDate: {
              gte: newViewingDate,
              lt: newNextDay,
            },
            viewingTime: newTime,
            status: {
              notIn: ["CANCELLED", "NO_SHOW"],
            },
            id: { not: viewing.id },
          },
        });

        if (conflicting) {
          return NextResponse.json({
            success: false,
            message: `There is already a viewing at ${newTime} on ${newDate}. Would you like to choose a different time?`,
          });
        }

        // Update the viewing
        await prisma.propertyViewing.update({
          where: { id: viewing.id },
          data: {
            viewingDate: newViewingDate,
            viewingTime: newTime,
            status: "CONFIRMED",
          },
        });

        scopedLogger.info("Viewing rescheduled via voice agent", {
          viewingId: viewing.id,
          newDate,
          newTime,
        });

        return NextResponse.json({
          success: true,
          message: `Your viewing for ${viewing.property.titleEn} has been rescheduled to ${newDate} at ${newTime}.`,
        });
      }

      default:
        return NextResponse.json({
          success: false,
          message: `Unknown action: ${effectiveAction}. I can help you book, cancel, or reschedule a viewing.`,
        });
    }
  } catch (error) {
    scopedLogger.error("Error in viewing tool", error);
    return NextResponse.json(
      {
        success: false,
        message:
          "Sorry, there was an error processing your viewing request. Please try again.",
      },
      { status: 500 },
    );
  }
}
