/**
 * Real Estate Viewings API
 * GET  - List viewings with filters (date range, brokerId, status, propertyId)
 * POST - Create viewing with conflict detection + auto broker assignment
 *
 * Supports both NextAuth and OTP (auth_token) authentication
 */

import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/db";
const ViewingStatus = {
  REQUESTED: "REQUESTED",
  CONFIRMED: "CONFIRMED",
  IN_PROGRESS: "IN_PROGRESS",
  VIEW_COMPLETED: "VIEW_COMPLETED",
  CANCELLED: "CANCELLED",
  NO_SHOW: "NO_SHOW",
  RESCHEDULED: "RESCHEDULED",
} as const;
type ViewingStatus = (typeof ViewingStatus)[keyof typeof ViewingStatus];

const ViewingType = {
  INDIVIDUAL: "INDIVIDUAL",
  OPEN_HOUSE: "OPEN_HOUSE",
  VIRTUAL: "VIRTUAL",
  PRIVATE: "PRIVATE",
} as const;
type ViewingType = (typeof ViewingType)[keyof typeof ViewingType];

const PropertyStatus = {
  COMING_SOON: "COMING_SOON",
  AVAILABLE: "AVAILABLE",
  UNDER_OFFER: "UNDER_OFFER",
  SOLD: "SOLD",
  RENTED: "RENTED",
  OFF_MARKET: "OFF_MARKET",
  EXPIRED: "EXPIRED",
} as const;
type PropertyStatus = (typeof PropertyStatus)[keyof typeof PropertyStatus];
import { getAuthenticatedEmail } from "@/lib/utils/auth-helpers.util";
import { getEffectiveOwnerId } from "@/lib/utils/organization-data";
import { logger } from "@/lib/logger";
import { createId } from "@paralleldrive/cuid2";
import { clampPaginationLimit } from "@/lib/config/constants";
import { ApiResponses, paginatedResponse } from "@/lib/utils/api-response.util";
import { withRateLimit, rateLimiters } from "@/lib/rate-limiter";
import { getErrorMessage } from "@/lib/errors";
import { withCSRF } from "@/lib/csrf";
import validator from "validator";
import { getDataSource } from "@/lib/data-source";

export const dynamic = "force-dynamic";

/**
 * GET /api/real-estate/viewings
 * Returns viewings with optional filters: dateFrom, dateTo, brokerId, status, propertyId
 * Includes property title and broker name in response.
 */
export const GET = withRateLimit(async (request: NextRequest) => {
  try {
    const userEmail = await getAuthenticatedEmail(request, auth);

    if (!userEmail) {
      logger.warn("Unauthorized viewings list 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 { convexQuery } = await import("@/lib/convex-client");
        const searchParams = request.nextUrl.searchParams;
        const status = searchParams.get("status");
        const brokerId = searchParams.get("brokerId");
        const propertyId = searchParams.get("propertyId");
        const viewings = await convexQuery("real_estate/queries:getViewings", {
          userId: effectiveUserId,
          ...(propertyId ? { propertyId } : {}),
          ...(brokerId ? { brokerId } : {}),
          ...(status && status !== "all" ? { status } : {}),
        });
        if (viewings !== null) {
          return paginatedResponse(
            viewings as unknown[],
            1,
            (viewings as unknown[]).length,
            (viewings as unknown[]).length,
          );
        }
      } catch (error) {
        console.error("[Convex fallback] real_estate viewings GET:", error);
      }
    }

    // Parse query params
    const searchParams = request.nextUrl.searchParams;
    const limit = clampPaginationLimit(searchParams.get("limit"));
    const page = Math.max(1, parseInt(searchParams.get("page") || "1", 10));
    const status = searchParams.get("status");
    const brokerId = searchParams.get("brokerId");
    const propertyId = searchParams.get("propertyId");
    const dateFrom = searchParams.get("dateFrom");
    const dateTo = searchParams.get("dateTo");

    // Build where clause
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const whereClause: any = {
      userId: effectiveUserId,
    };

    if (status && status !== "all") {
      whereClause.status = status as ViewingStatus;
    }

    if (brokerId) {
      whereClause.brokerId = brokerId;
    }

    if (propertyId) {
      whereClause.propertyId = propertyId;
    }

    if (dateFrom || dateTo) {
      whereClause.viewingDate = {};
      if (dateFrom) {
        whereClause.viewingDate.gte = new Date(dateFrom);
      }
      if (dateTo) {
        whereClause.viewingDate.lte = new Date(dateTo);
      }
    }

    // Fetch total count and viewings in parallel
    const [totalCount, viewings] = await Promise.all([
      prisma.propertyViewing.count({ where: whereClause }),
      prisma.propertyViewing.findMany({
        where: whereClause,
        include: {
          property: {
            select: {
              id: true,
              titleEn: true,
              titleAr: true,
              referenceNumber: true,
              propertyType: true,
              city: true,
              district: true,
            },
          },
          broker: {
            select: {
              id: true,
              nameEn: true,
              nameAr: true,
              phone: true,
            },
          },
        },
        orderBy: [{ viewingDate: "asc" }, { viewingTime: "asc" }],
        skip: (page - 1) * limit,
        take: limit,
      }),
    ]);

    logger.info("Viewings retrieved", {
      userEmail,
      count: viewings.length,
      totalCount,
    });

    return paginatedResponse(viewings, page, limit, totalCount);
  } catch (error) {
    logger.error("Viewings list error", {
      error: getErrorMessage(error),
    });
    return ApiResponses.serverError("Failed to retrieve viewings");
  }
}, rateLimiters.authGeneral);

/**
 * POST /api/real-estate/viewings
 * Creates a new property viewing with conflict detection and optional auto broker assignment.
 *
 * Conflict detection rules:
 * 1. Property must be AVAILABLE or UNDER_OFFER
 * 2. No existing viewing for same property + date + time (unless OPEN_HOUSE)
 * 3. Assigned broker must not be double-booked (same date + overlapping time)
 * 4. OPEN_HOUSE viewings check maxAttendees capacity and increment currentAttendees
 * 5. Auto-assigns broker if brokerId not provided (fewest viewings that day)
 */
export const POST = withCSRF(
  withRateLimit(async (request: NextRequest) => {
    try {
      const userEmail = await getAuthenticatedEmail(request, auth);

      if (!userEmail) {
        logger.warn("Unauthorized viewing creation attempt");
        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 body = await request.json();
          const { convexMutation } = await import("@/lib/convex-client");
          const result = await convexMutation(
            "real_estate/mutations:scheduleViewing",
            {
              userId: effectiveUserId,
              propertyId: body.propertyId,
              brokerId: body.brokerId,
              clientName: body.clientName?.trim(),
              clientPhone: body.clientPhone?.trim(),
              clientEmail: body.clientEmail?.trim(),
              viewingType: body.viewingType,
              viewingDate: new Date(body.viewingDate).getTime(),
              viewingTime: body.viewingTime,
              duration: body.duration,
            },
          );
          return NextResponse.json(
            { success: true, viewing: result, autoAssignedBroker: false },
            { status: 201 },
          );
        } catch (error) {
          console.error("[Convex fallback] real_estate viewings POST:", error);
        }
      }

      const body = await request.json();
      const {
        propertyId,
        brokerId,
        clientName,
        clientPhone,
        clientEmail,
        viewingType = "INDIVIDUAL",
        viewingDate,
        viewingTime,
        duration = 30,
        meetingLink,
        clientTimezone,
        maxAttendees,
        notes,
        source = "manual",
        conversationId,
      } = body;

      // --- Input Validation ---
      const errors: string[] = [];

      if (!propertyId || typeof propertyId !== "string") {
        errors.push("Property ID is required");
      }

      if (!clientName || clientName.trim().length < 2) {
        errors.push(
          "Client name is required and must be at least 2 characters",
        );
      }

      if (!clientPhone || clientPhone.trim().length < 5) {
        errors.push("Client phone number is required");
      }

      if (clientEmail && !validator.isEmail(clientEmail)) {
        errors.push("Invalid email format");
      }

      if (!viewingDate) {
        errors.push("Viewing date is required");
      }

      if (!viewingTime) {
        errors.push("Viewing time is required");
      }

      if (
        viewingTime &&
        !/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(viewingTime)
      ) {
        errors.push("Invalid time format. Use HH:MM format");
      }

      const parsedDate = new Date(viewingDate);
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      if (viewingDate && parsedDate < today) {
        errors.push("Viewing date cannot be in the past");
      }

      if (duration && (duration < 10 || duration > 240)) {
        errors.push("Duration must be between 10 and 240 minutes");
      }

      if (
        viewingType &&
        !Object.values(ViewingType).includes(viewingType as ViewingType)
      ) {
        errors.push(
          `Invalid viewing type. Must be one of: ${Object.values(ViewingType).join(", ")}`,
        );
      }

      if (viewingType === "OPEN_HOUSE" && !maxAttendees) {
        errors.push("Max attendees is required for open house viewings");
      }

      if (viewingType === "VIRTUAL" && !meetingLink) {
        errors.push("Meeting link is required for virtual viewings");
      }

      if (errors.length > 0) {
        return NextResponse.json(
          { success: false, error: errors.join(", "), errors },
          { status: 400 },
        );
      }

      // --- Property status check ---
      const property = await prisma.property.findFirst({
        where: {
          id: propertyId,
          userId: effectiveUserId,
        },
        select: { id: true, titleEn: true, status: true },
      });

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

      const allowedStatuses: PropertyStatus[] = [
        PropertyStatus.AVAILABLE,
        PropertyStatus.UNDER_OFFER,
      ];
      if (!allowedStatuses.includes(property.status)) {
        return NextResponse.json(
          {
            success: false,
            error: `Property is not available for viewings (current status: ${property.status})`,
            errorCode: "PROPERTY_UNAVAILABLE",
          },
          { status: 409 },
        );
      }

      // Get organization for uniqueness checks
      const organization = await prisma.organizations.findUnique({
        where: { ownerId: effectiveUserId },
        select: { id: true },
      });

      // --- Auto broker assignment if no brokerId provided ---
      let assignedBrokerId = brokerId || null;

      if (!assignedBrokerId) {
        const dayOfWeek = parsedDate.getDay();

        const availableBrokers = await prisma.broker.findMany({
          where: {
            userId: effectiveUserId,
            isActive: true,
          },
          select: {
            id: true,
            workingDays: true,
            workStartTime: true,
            workEndTime: true,
            maxViewingsPerDay: true,
            _count: {
              select: {
                viewings: {
                  where: {
                    viewingDate: parsedDate,
                    status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
                  },
                },
              },
            },
          },
        });

        // Filter brokers who work on this day and have capacity
        const eligible = availableBrokers.filter((broker) => {
          const workingDays = broker.workingDays as number[];
          const isWorkingDay = workingDays.includes(dayOfWeek);
          const hasCapacity = broker._count.viewings < broker.maxViewingsPerDay;
          const isWithinHours =
            viewingTime >= broker.workStartTime &&
            viewingTime <= broker.workEndTime;
          return isWorkingDay && hasCapacity && isWithinHours;
        });

        if (eligible.length > 0) {
          // Assign the broker with fewest viewings that day
          eligible.sort((a, b) => a._count.viewings - b._count.viewings);
          assignedBrokerId = eligible[0].id;
        }
      }

      // --- Verify broker exists if specified ---
      if (assignedBrokerId) {
        const broker = await prisma.broker.findFirst({
          where: {
            id: assignedBrokerId,
            userId: effectiveUserId,
            isActive: true,
          },
          select: { id: true },
        });

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

      // --- Create viewing inside transaction with conflict detection ---
      const viewingId = createId();

      const viewing = await prisma.$transaction(async (tx) => {
        // 1. Check broker is not double-booked at the same date+time
        if (assignedBrokerId) {
          const brokerConflict = await tx.propertyViewing.findFirst({
            where: {
              brokerId: assignedBrokerId,
              viewingDate: parsedDate,
              viewingTime: viewingTime,
              status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
            },
          });

          if (brokerConflict) {
            throw new Error("BROKER_CONFLICT");
          }
        }

        // 2. Check property is not already booked at same date+time (unless OPEN_HOUSE)
        if (viewingType !== "OPEN_HOUSE") {
          const propertyConflict = await tx.propertyViewing.findFirst({
            where: {
              propertyId: propertyId,
              viewingDate: parsedDate,
              viewingTime: viewingTime,
              status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
              viewingType: { not: "OPEN_HOUSE" },
            },
          });

          if (propertyConflict) {
            throw new Error("PROPERTY_CONFLICT");
          }
        }

        // 3. OPEN_HOUSE capacity check: ensure maxAttendees not exceeded, increment currentAttendees
        let currentAttendeesValue = 0;
        if (viewingType === "OPEN_HOUSE") {
          // Find existing open house at same property/date/time
          const existingOpenHouse = await tx.propertyViewing.findFirst({
            where: {
              propertyId: propertyId,
              viewingDate: parsedDate,
              viewingTime: viewingTime,
              viewingType: "OPEN_HOUSE",
              status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
            },
            select: { id: true, maxAttendees: true, currentAttendees: true },
          });

          if (existingOpenHouse) {
            if (
              existingOpenHouse.maxAttendees &&
              existingOpenHouse.currentAttendees >=
                existingOpenHouse.maxAttendees
            ) {
              throw new Error("OPEN_HOUSE_FULL");
            }

            // Increment currentAttendees on existing open house
            await tx.propertyViewing.update({
              where: { id: existingOpenHouse.id },
              data: { currentAttendees: { increment: 1 } },
            });
          }

          currentAttendeesValue = 1;
        }

        return tx.propertyViewing.create({
          data: {
            id: viewingId,
            userId: effectiveUserId,
            organizationId: organization?.id || null,
            propertyId: propertyId,
            brokerId: assignedBrokerId,
            clientName: clientName.trim(),
            clientPhone: clientPhone.trim(),
            clientEmail: clientEmail?.trim() || null,
            viewingType: viewingType as ViewingType,
            viewingDate: parsedDate,
            viewingTime: viewingTime,
            duration: duration,
            status: "REQUESTED",
            meetingLink: meetingLink?.trim() || null,
            clientTimezone: clientTimezone?.trim() || null,
            maxAttendees: maxAttendees || null,
            currentAttendees:
              viewingType === "OPEN_HOUSE" ? currentAttendeesValue : 0,
            source: source,
            conversationId: conversationId || null,
            clientNotes: notes?.trim() || null,
          },
          include: {
            property: {
              select: {
                id: true,
                titleEn: true,
                titleAr: true,
                referenceNumber: true,
              },
            },
            broker: {
              select: {
                id: true,
                nameEn: true,
                nameAr: true,
                phone: true,
              },
            },
          },
        });
      });

      logger.info("Property viewing created", {
        viewingId: viewing.id,
        userEmail,
        propertyId,
        brokerId: assignedBrokerId,
        viewingDate: viewing.viewingDate.toISOString(),
        autoAssigned: !brokerId && !!assignedBrokerId,
      });

      return NextResponse.json(
        {
          success: true,
          viewing,
          autoAssignedBroker: !brokerId && !!assignedBrokerId,
        },
        { status: 201 },
      );
    } catch (error) {
      if (error instanceof Error && error.message === "BROKER_CONFLICT") {
        return NextResponse.json(
          {
            success: false,
            error: "This broker already has a viewing at the selected time",
            errorCode: "BROKER_CONFLICT",
          },
          { status: 409 },
        );
      }

      if (error instanceof Error && error.message === "PROPERTY_CONFLICT") {
        return NextResponse.json(
          {
            success: false,
            error:
              "This property already has a viewing scheduled at the selected time",
            errorCode: "PROPERTY_CONFLICT",
          },
          { status: 409 },
        );
      }

      if (error instanceof Error && error.message === "OPEN_HOUSE_FULL") {
        return NextResponse.json(
          {
            success: false,
            error: "This open house has reached maximum capacity",
            errorCode: "OPEN_HOUSE_FULL",
          },
          { status: 409 },
        );
      }

      logger.error("Failed to create viewing", {
        error: getErrorMessage(error),
      });

      return ApiResponses.serverError("Failed to create viewing");
    }
  }, rateLimiters.authGeneral),
);
