/**
 * Real Estate Viewing Detail API
 * GET    - Get viewing by ID with property, broker, and feedback details
 * PUT    - Update viewing (reschedule, status change) with conflict re-validation
 * DELETE - Cancel viewing (soft delete via status change); decrement OPEN_HOUSE currentAttendees
 *
 * 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];
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 { withCSRF } from "@/lib/csrf";
import { getDataSource } from "@/lib/data-source";

export const dynamic = "force-dynamic";

/**
 * GET /api/real-estate/viewings/[id]
 * Returns a single viewing with full property, broker, tour, and feedback details
 */
export const GET = withRateLimit(
  async (
    request: NextRequest,
    { params }: { params: Promise<{ id: string }> },
  ) => {
    try {
      const userEmail = await getAuthenticatedEmail(request, auth);

      if (!userEmail) {
        logger.warn("Unauthorized viewing detail 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);
      const { id } = await params;

      if (!id || typeof id !== "string") {
        return ApiResponses.badRequest("Viewing ID is required");
      }

      // Convex branch
      if (getDataSource("real_estate") === "convex") {
        try {
          const { convexQuery } = await import("@/lib/convex-client");
          const result = await convexQuery(
            "real_estate/queries:getViewingById",
            { id, userId: effectiveUserId },
          );
          if (result) {
            return NextResponse.json({ success: true, viewing: result });
          }
          return ApiResponses.notFound("Viewing not found");
        } catch (error) {
          console.error("[Convex fallback] real_estate viewing GET:", error);
        }
      }

      const viewing = await prisma.propertyViewing.findFirst({
        where: {
          id,
          userId: effectiveUserId,
        },
        include: {
          property: {
            select: {
              id: true,
              titleEn: true,
              titleAr: true,
              referenceNumber: true,
              propertyType: true,
              listingType: true,
              status: true,
              price: true,
              currency: true,
              bedrooms: true,
              bathrooms: true,
              builtUpArea: true,
              city: true,
              district: true,
              buildingName: true,
              unitNumber: true,
              latitude: true,
              longitude: true,
            },
          },
          broker: {
            select: {
              id: true,
              nameEn: true,
              nameAr: true,
              phone: true,
              email: true,
              whatsappNumber: true,
              photoUrl: true,
            },
          },
          feedback: true,
          tour: {
            select: {
              id: true,
              clientName: true,
              tourDate: true,
              startTime: true,
              estimatedEnd: true,
              status: true,
            },
          },
        },
      });

      if (!viewing) {
        return ApiResponses.notFound("Viewing not found");
      }

      logger.info("Viewing detail retrieved", {
        viewingId: id,
        userEmail,
      });

      return NextResponse.json({
        success: true,
        viewing,
      });
    } catch (error) {
      logger.error("Viewing detail error", {
        error: getErrorMessage(error),
      });
      return ApiResponses.serverError("Failed to retrieve viewing");
    }
  },
  rateLimiters.authGeneral,
);

/**
 * PUT /api/real-estate/viewings/[id]
 * Update viewing: reschedule (date/time change triggers re-validation), change status, update notes.
 * Re-runs conflict detection when date or time changes.
 */
export const PUT = withCSRF(
  withRateLimit(
    async (
      request: NextRequest,
      { params }: { params: Promise<{ id: string }> },
    ) => {
      try {
        const userEmail = await getAuthenticatedEmail(request, auth);

        if (!userEmail) {
          logger.warn("Unauthorized viewing update 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);
        const { id } = await params;

        if (!id || typeof id !== "string") {
          return ApiResponses.badRequest("Viewing ID is required");
        }

        // Convex branch
        if (getDataSource("real_estate") === "convex") {
          try {
            const body = await request.json();
            const { convexMutation } = await import("@/lib/convex-client");
            if (body.status) {
              await convexMutation(
                "real_estate/mutations:updateViewingStatus",
                { id: id as any, status: body.status },
              );
            }
            return NextResponse.json({
              success: true,
              viewing: { id, ...body },
              rescheduled: false,
            });
          } catch (error) {
            console.error("[Convex fallback] real_estate viewing PUT:", error);
          }
        }

        // Verify viewing exists and belongs to user
        const existingViewing = await prisma.propertyViewing.findFirst({
          where: {
            id,
            userId: effectiveUserId,
          },
          select: {
            id: true,
            status: true,
            brokerId: true,
            propertyId: true,
            viewingDate: true,
            viewingTime: true,
            viewingType: true,
          },
        });

        if (!existingViewing) {
          return ApiResponses.notFound("Viewing not found");
        }

        const body = await request.json();
        const {
          status,
          viewingDate,
          viewingTime,
          duration,
          brokerId,
          viewingType,
          meetingLink,
          clientTimezone,
          maxAttendees,
          clientNotes,
          interestLevel,
          accessConfirmed,
          keyCollected,
        } = body;

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

        if (status && !Object.values(ViewingStatus).includes(status)) {
          errors.push(
            `Invalid status. Must be one of: ${Object.values(ViewingStatus).join(", ")}`,
          );
        }

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

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

        if (viewingDate) {
          const parsedDate = new Date(viewingDate);
          const today = new Date();
          today.setHours(0, 0, 0, 0);
          if (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 (errors.length > 0) {
          return NextResponse.json(
            { success: false, error: errors.join(", "), errors },
            { status: 400 },
          );
        }

        // Determine if this is a reschedule (date or time change)
        const isReschedule =
          (viewingDate || viewingTime) &&
          existingViewing.status !== "CANCELLED";

        // Build update data
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const updateData: any = {};

        if (status) updateData.status = status;
        if (viewingDate) updateData.viewingDate = new Date(viewingDate);
        if (viewingTime) updateData.viewingTime = viewingTime;
        if (duration !== undefined) updateData.duration = duration;
        if (brokerId !== undefined) updateData.brokerId = brokerId || null;
        if (viewingType) updateData.viewingType = viewingType;
        if (meetingLink !== undefined)
          updateData.meetingLink = meetingLink?.trim() || null;
        if (clientTimezone !== undefined)
          updateData.clientTimezone = clientTimezone?.trim() || null;
        if (maxAttendees !== undefined) updateData.maxAttendees = maxAttendees;
        if (clientNotes !== undefined)
          updateData.clientNotes = clientNotes?.trim() || null;
        if (interestLevel !== undefined)
          updateData.interestLevel = interestLevel?.trim() || null;
        if (accessConfirmed !== undefined)
          updateData.accessConfirmed = accessConfirmed;
        if (keyCollected !== undefined) updateData.keyCollected = keyCollected;

        // If rescheduling, set status to RESCHEDULED unless explicitly overridden
        if (isReschedule && !status) {
          updateData.status = "RESCHEDULED";
        }

        // Conflict detection on reschedule (inside transaction)
        const updatedViewing = await prisma.$transaction(async (tx) => {
          if (isReschedule) {
            const targetDate = viewingDate
              ? new Date(viewingDate)
              : existingViewing.viewingDate;
            const targetTime = viewingTime || existingViewing.viewingTime;
            const targetBrokerId =
              brokerId !== undefined ? brokerId : existingViewing.brokerId;

            // Check broker conflict
            if (targetBrokerId) {
              const brokerConflict = await tx.propertyViewing.findFirst({
                where: {
                  id: { not: id },
                  brokerId: targetBrokerId,
                  viewingDate: targetDate,
                  viewingTime: targetTime,
                  status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
                },
              });

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

            // Check property conflict (unless OPEN_HOUSE)
            const effectiveViewingType =
              viewingType || existingViewing.viewingType;
            if (effectiveViewingType !== "OPEN_HOUSE") {
              const propertyConflict = await tx.propertyViewing.findFirst({
                where: {
                  id: { not: id },
                  propertyId: existingViewing.propertyId,
                  viewingDate: targetDate,
                  viewingTime: targetTime,
                  status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
                  viewingType: { not: "OPEN_HOUSE" },
                },
              });

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

          return tx.propertyViewing.update({
            where: { id },
            data: updateData,
            include: {
              property: {
                select: {
                  id: true,
                  titleEn: true,
                  titleAr: true,
                  referenceNumber: true,
                },
              },
              broker: {
                select: {
                  id: true,
                  nameEn: true,
                  nameAr: true,
                  phone: true,
                },
              },
            },
          });
        });

        logger.info("Viewing updated", {
          viewingId: id,
          userEmail,
          isReschedule,
          newStatus: updatedViewing.status,
        });

        return NextResponse.json({
          success: true,
          viewing: updatedViewing,
          rescheduled: isReschedule,
        });
      } 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 },
          );
        }

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

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

/**
 * DELETE /api/real-estate/viewings/[id]
 * Cancels a viewing (soft delete - sets status to CANCELLED).
 * For OPEN_HOUSE viewings: decrements currentAttendees on the original open house.
 */
export const DELETE = withCSRF(
  withRateLimit(
    async (
      request: NextRequest,
      { params }: { params: Promise<{ id: string }> },
    ) => {
      try {
        const userEmail = await getAuthenticatedEmail(request, auth);

        if (!userEmail) {
          logger.warn("Unauthorized viewing cancellation 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);
        const { id } = await params;

        if (!id || typeof id !== "string") {
          return ApiResponses.badRequest("Viewing ID is required");
        }

        // Convex branch (DELETE)
        if (getDataSource("real_estate") === "convex") {
          try {
            const { convexMutation } = await import("@/lib/convex-client");
            await convexMutation("real_estate/mutations:updateViewingStatus", {
              id: id as any,
              status: "CANCELLED",
            });
            return NextResponse.json({
              success: true,
              viewing: { id, status: "CANCELLED" },
            });
          } catch (error) {
            console.error(
              "[Convex fallback] real_estate viewing DELETE:",
              error,
            );
          }
        }

        const existingViewing = await prisma.propertyViewing.findFirst({
          where: {
            id,
            userId: effectiveUserId,
          },
          select: {
            id: true,
            status: true,
            viewingType: true,
            propertyId: true,
            viewingDate: true,
            viewingTime: true,
          },
        });

        if (!existingViewing) {
          return ApiResponses.notFound("Viewing not found");
        }

        if (existingViewing.status === "CANCELLED") {
          return ApiResponses.badRequest("Viewing is already cancelled");
        }

        if (existingViewing.status === "VIEW_COMPLETED") {
          return ApiResponses.badRequest("Cannot cancel a completed viewing");
        }

        const body = await request.json().catch(() => ({}));
        const cancellationReason =
          typeof body?.reason === "string" ? body.reason.trim() : null;

        // Use transaction for OPEN_HOUSE decrement atomicity
        const cancelledViewing = await prisma.$transaction(async (tx) => {
          // For OPEN_HOUSE: decrement currentAttendees on sibling open house viewings
          if (existingViewing.viewingType === "OPEN_HOUSE") {
            // Find an active sibling open house viewing at the same property/date/time
            // (there may be one "master" open house record with the attendee count)
            const siblingOpenHouse = await tx.propertyViewing.findFirst({
              where: {
                id: { not: id },
                propertyId: existingViewing.propertyId,
                viewingDate: existingViewing.viewingDate,
                viewingTime: existingViewing.viewingTime,
                viewingType: "OPEN_HOUSE",
                status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
                currentAttendees: { gt: 0 },
              },
              select: { id: true },
            });

            if (siblingOpenHouse) {
              await tx.propertyViewing.update({
                where: { id: siblingOpenHouse.id },
                data: { currentAttendees: { decrement: 1 } },
              });
            }
          }

          return tx.propertyViewing.update({
            where: { id },
            data: {
              status: "CANCELLED",
              clientNotes: cancellationReason
                ? `[CANCELLED] ${cancellationReason}`
                : undefined,
            },
            select: {
              id: true,
              status: true,
              viewingDate: true,
              viewingTime: true,
              viewingType: true,
              propertyId: true,
              brokerId: true,
            },
          });
        });

        logger.info("Viewing cancelled", {
          viewingId: id,
          userEmail,
          viewingType: existingViewing.viewingType,
          reason: cancellationReason,
        });

        return NextResponse.json({
          success: true,
          viewing: cancelledViewing,
        });
      } catch (error) {
        logger.error("Failed to cancel viewing", {
          error: getErrorMessage(error),
        });

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