/**
 * Viewing Tours API
 * POST - Create a viewing tour (multiple properties in one session)
 * GET  - List tours for the authenticated user
 *
 * A ViewingTour groups multiple PropertyViewing records together with
 * 30-minute slots per property and 15-minute travel time between them.
 *
 * 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 { 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 { getDataSource } from "@/lib/data-source";

export const dynamic = "force-dynamic";

/** Duration per property viewing in minutes */
const VIEWING_DURATION_MINUTES = 30;
/** Travel time between properties in minutes */
const TRAVEL_TIME_MINUTES = 15;

/**
 * Add minutes to an HH:MM time string and return a new HH:MM string.
 */
function addMinutesToTime(time: string, minutes: number): string {
  const [h, m] = time.split(":").map(Number);
  const totalMinutes = h * 60 + m + minutes;
  const newH = Math.floor(totalMinutes / 60) % 24;
  const newM = totalMinutes % 60;
  return `${String(newH).padStart(2, "0")}:${String(newM).padStart(2, "0")}`;
}

/**
 * GET /api/real-estate/viewings/tours
 * List viewing tours for the authenticated user.
 * Supports pagination via ?page=&limit= query params.
 */
export const GET = withRateLimit(async (request: NextRequest) => {
  try {
    const userEmail = await getAuthenticatedEmail(request, auth);

    if (!userEmail) {
      logger.warn("Unauthorized tours 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 tours = await convexQuery("real_estate/queries:getTours", {
          userId: effectiveUserId,
        });
        if (tours !== null) {
          return paginatedResponse(
            tours as unknown[],
            1,
            (tours as unknown[]).length,
            (tours as unknown[]).length,
          );
        }
      } catch (error) {
        console.error("[Convex fallback] real_estate tours GET:", error);
      }
    }

    const searchParams = request.nextUrl.searchParams;
    const limit = clampPaginationLimit(searchParams.get("limit"));
    const page = Math.max(1, parseInt(searchParams.get("page") || "1", 10));

    const whereClause = { userId: effectiveUserId };

    const [totalCount, tours] = await Promise.all([
      prisma.viewingTour.count({ where: whereClause }),
      prisma.viewingTour.findMany({
        where: whereClause,
        include: {
          viewings: {
            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: { viewingTime: "asc" },
          },
        },
        orderBy: [{ tourDate: "desc" }, { startTime: "asc" }],
        skip: (page - 1) * limit,
        take: limit,
      }),
    ]);

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

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

/**
 * POST /api/real-estate/viewings/tours
 * Create a viewing tour.
 *
 * Request body:
 * {
 *   clientName: string,
 *   clientPhone: string,
 *   clientEmail?: string,
 *   propertyIds: string[],
 *   tourDate: string (ISO date),
 *   startTime: string (HH:MM),
 *   brokerId?: string,
 *   notes?: string
 * }
 *
 * Creates a ViewingTour record and individual PropertyViewing records for each
 * property. Each viewing is 30 minutes with 15 minutes travel time in between.
 * Calculates estimatedEnd from startTime + total duration.
 */
export const POST = withCSRF(
  withRateLimit(async (request: NextRequest) => {
    try {
      const userEmail = await getAuthenticatedEmail(request, auth);

      if (!userEmail) {
        logger.warn("Unauthorized tour 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 - tours POST falls through to Prisma because
      // tour creation requires transactional multi-record creation
      // which is better handled by Prisma's $transaction.
      // The Convex scheduleViewing mutation handles individual viewings.

      const body = await request.json();
      const {
        clientName,
        clientPhone,
        clientEmail,
        propertyIds,
        tourDate,
        startTime,
        brokerId,
        notes,
      } = body;

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

      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 (!Array.isArray(propertyIds) || propertyIds.length === 0) {
        errors.push("At least one property ID is required");
      }

      if (Array.isArray(propertyIds) && propertyIds.length > 10) {
        errors.push("A tour can include a maximum of 10 properties");
      }

      if (!tourDate) {
        errors.push("Tour date is required");
      }

      if (!startTime) {
        errors.push("Start time is required");
      }

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

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

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

      const parsedTourDate = new Date(tourDate);

      // Verify all properties exist and belong to the user
      const properties = await prisma.property.findMany({
        where: {
          id: { in: propertyIds },
          userId: effectiveUserId,
        },
        select: { id: true, titleEn: true, status: true },
      });

      if (properties.length !== propertyIds.length) {
        const foundIds = new Set(properties.map((p) => p.id));
        const missing = (propertyIds as string[]).filter(
          (pid) => !foundIds.has(pid),
        );
        return NextResponse.json(
          {
            success: false,
            error: `Properties not found: ${missing.join(", ")}`,
            errorCode: "PROPERTIES_NOT_FOUND",
          },
          { status: 404 },
        );
      }

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

      // Verify/auto-assign broker
      let assignedBrokerId = brokerId || null;
      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");
        }
      } else {
        // Auto-assign: pick first available active broker with fewest viewings that day
        const dayOfWeek = parsedTourDate.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: parsedTourDate,
                    status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
                  },
                },
              },
            },
          },
        });

        const eligible = availableBrokers.filter((broker) => {
          const workingDays = broker.workingDays as number[];
          const isWorkingDay = workingDays.includes(dayOfWeek);
          // Broker needs capacity for all properties in the tour
          const hasCapacity =
            broker._count.viewings + propertyIds.length <=
            broker.maxViewingsPerDay;
          const isWithinHours =
            startTime >= broker.workStartTime &&
            startTime <= broker.workEndTime;
          return isWorkingDay && hasCapacity && isWithinHours;
        });

        if (eligible.length > 0) {
          eligible.sort((a, b) => a._count.viewings - b._count.viewings);
          assignedBrokerId = eligible[0].id;
        }
      }

      // Calculate estimatedEnd time
      const totalMinutes =
        (propertyIds as string[]).length * VIEWING_DURATION_MINUTES +
        ((propertyIds as string[]).length - 1) * TRAVEL_TIME_MINUTES;
      const estimatedEnd = addMinutesToTime(startTime, totalMinutes);

      // Create tour and individual viewings in a transaction
      const tour = await prisma.$transaction(async (tx) => {
        const tourId = createId();

        // Create the ViewingTour record
        const newTour = await tx.viewingTour.create({
          data: {
            id: tourId,
            userId: effectiveUserId,
            organizationId: organization?.id || null,
            brokerId: assignedBrokerId,
            clientName: clientName.trim(),
            clientPhone: clientPhone.trim(),
            tourDate: parsedTourDate,
            startTime: startTime,
            estimatedEnd: estimatedEnd,
            notes: notes?.trim() || null,
            status: "scheduled",
          },
        });

        // Create individual PropertyViewing records
        // Each viewing is 30 min, with 15 min travel in between
        let currentTime = startTime;
        const viewingRecords = [];

        for (let i = 0; i < (propertyIds as string[]).length; i++) {
          const propertyId = (propertyIds as string[])[i];

          // Check broker conflict at this specific time slot
          if (assignedBrokerId) {
            const brokerConflict = await tx.propertyViewing.findFirst({
              where: {
                brokerId: assignedBrokerId,
                viewingDate: parsedTourDate,
                viewingTime: currentTime,
                status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
              },
            });

            if (brokerConflict) {
              throw new Error(`BROKER_CONFLICT_AT_${currentTime}`);
            }
          }

          const viewingRecord = await tx.propertyViewing.create({
            data: {
              id: createId(),
              userId: effectiveUserId,
              organizationId: organization?.id || null,
              propertyId: propertyId,
              brokerId: assignedBrokerId,
              tourId: tourId,
              clientName: clientName.trim(),
              clientPhone: clientPhone.trim(),
              clientEmail: clientEmail?.trim() || null,
              viewingType: "TOUR",
              viewingDate: parsedTourDate,
              viewingTime: currentTime,
              duration: VIEWING_DURATION_MINUTES,
              status: "REQUESTED",
              source: "tour",
            },
            include: {
              property: {
                select: {
                  id: true,
                  titleEn: true,
                  titleAr: true,
                  referenceNumber: true,
                },
              },
            },
          });

          viewingRecords.push(viewingRecord);

          // Move to next time slot (30 min viewing + 15 min travel)
          if (i < (propertyIds as string[]).length - 1) {
            currentTime = addMinutesToTime(
              currentTime,
              VIEWING_DURATION_MINUTES + TRAVEL_TIME_MINUTES,
            );
          }
        }

        return {
          ...newTour,
          viewings: viewingRecords,
        };
      });

      logger.info("Viewing tour created", {
        tourId: tour.id,
        userEmail,
        propertyCount: (propertyIds as string[]).length,
        brokerId: assignedBrokerId,
        startTime,
        estimatedEnd,
        autoAssigned: !brokerId && !!assignedBrokerId,
      });

      return NextResponse.json(
        {
          success: true,
          tour,
          autoAssignedBroker: !brokerId && !!assignedBrokerId,
        },
        { status: 201 },
      );
    } catch (error) {
      if (
        error instanceof Error &&
        error.message.startsWith("BROKER_CONFLICT_AT_")
      ) {
        const time = error.message.replace("BROKER_CONFLICT_AT_", "");
        return NextResponse.json(
          {
            success: false,
            error: `Assigned broker has a conflict at ${time}`,
            errorCode: "BROKER_CONFLICT",
          },
          { status: 409 },
        );
      }

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

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