/**
 * Real Estate Property Detail API
 * GET    - Retrieve a single property by ID (with images)
 * PUT    - Update an existing property
 * DELETE - Soft-delete (archive) a property – blocked when active viewings exist
 *
 * 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 PropertyType = {
  APARTMENT: "APARTMENT",
  VILLA: "VILLA",
  TOWNHOUSE: "TOWNHOUSE",
  PENTHOUSE: "PENTHOUSE",
  STUDIO: "STUDIO",
  DUPLEX: "DUPLEX",
  COMPOUND: "COMPOUND",
  LAND: "LAND",
  COMMERCIAL_OFFICE: "COMMERCIAL_OFFICE",
  COMMERCIAL_RETAIL: "COMMERCIAL_RETAIL",
  COMMERCIAL_WAREHOUSE: "COMMERCIAL_WAREHOUSE",
  COMMERCIAL_SHOWROOM: "COMMERCIAL_SHOWROOM",
  BUILDING: "BUILDING",
  FARM: "FARM",
  OTHER: "OTHER",
} as const;
type PropertyType = (typeof PropertyType)[keyof typeof PropertyType];

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];

const ListingType = { SALE: "SALE", RENT: "RENT" } as const;
type ListingType = (typeof ListingType)[keyof typeof ListingType];

const FurnishingType = {
  FURNISHED: "FURNISHED",
  SEMI_FURNISHED: "SEMI_FURNISHED",
  UNFURNISHED: "UNFURNISHED",
} as const;
type FurnishingType = (typeof FurnishingType)[keyof typeof FurnishingType];

const OwnershipType = {
  FREEHOLD: "FREEHOLD",
  LEASEHOLD: "LEASEHOLD",
  USUFRUCT: "USUFRUCT",
} as const;
type OwnershipType = (typeof OwnershipType)[keyof typeof OwnershipType];

const GCCCurrencyCode = {
  QAR: "QAR",
  SAR: "SAR",
  AED: "AED",
  KWD: "KWD",
  BHD: "BHD",
  OMR: "OMR",
  USD: "USD",
} as const;
type GCCCurrencyCode = (typeof GCCCurrencyCode)[keyof typeof GCCCurrencyCode];
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";
import validator from "validator";
import {
  handlePriceChange,
  handleStatusChange,
} from "@/lib/services/real-estate/property-update-triggers.service";

export const dynamic = "force-dynamic";

// ── Helpers ──────────────────────────────────────────────────────────

function isValidEnum<T extends Record<string, string>>(
  enumObj: T,
  value: unknown,
): value is T[keyof T] {
  return typeof value === "string" && Object.values(enumObj).includes(value);
}

function sanitizeText(raw: unknown, maxLength = 500): string | null {
  if (typeof raw !== "string") return null;
  const trimmed = raw.trim().replace(/\s+/g, " ");
  if (trimmed.length === 0) return null;
  return validator.escape(trimmed).slice(0, maxLength);
}

type RouteContext = { params: Promise<{ id: string }> };

/**
 * Valid status transitions for properties.
 * Key = current status, Value = set of statuses it can transition to.
 */
const VALID_STATUS_TRANSITIONS: Record<string, string[]> = {
  COMING_SOON: ["AVAILABLE", "OFF_MARKET"],
  AVAILABLE: ["UNDER_OFFER", "SOLD", "RENTED", "OFF_MARKET", "EXPIRED"],
  UNDER_OFFER: ["AVAILABLE", "SOLD", "RENTED", "OFF_MARKET"],
  SOLD: ["OFF_MARKET"],
  RENTED: ["AVAILABLE", "OFF_MARKET", "EXPIRED"],
  OFF_MARKET: ["COMING_SOON", "AVAILABLE"],
  EXPIRED: ["AVAILABLE", "OFF_MARKET"],
};

// ── GET /api/real-estate/properties/[id] ────────────────────────────

/**
 * GET /api/real-estate/properties/[id]
 * Returns a single property with all images
 */
export const GET = withRateLimit(
  async (request: NextRequest, context: RouteContext) => {
    try {
      const { id } = await context.params;

      // Authenticate
      const userEmail = await getAuthenticatedEmail(request, auth);
      if (!userEmail) {
        logger.warn("Unauthorized property 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);

      // Validate ID format
      if (!id || typeof id !== "string" || id.length < 5 || id.length > 50) {
        return ApiResponses.badRequest("Invalid property ID");
      }

      // ── Convex data-source branch ──────────────────────────────────
      if (getDataSource("real_estate") === "convex") {
        try {
          const { convexQuery } = await import("@/lib/convex-client");

          const property = await convexQuery(
            "real_estate/queries:getPropertyById",
            { id: id as any },
          );

          if (property) {
            // Fetch images separately
            logger.info("Property retrieved via Convex", {
              propertyId: id,
              userEmail,
            });
            return NextResponse.json({ success: true, property });
          }
        } catch (error) {
          console.error(
            "[Convex fallback] real_estate properties/[id] GET:",
            error,
          );
          // Fall through to Prisma
        }
      }

      // Fetch property scoped to user / org
      const property = await prisma.property.findFirst({
        where: {
          id,
          userId: effectiveUserId,
        },
        include: {
          images: {
            orderBy: { sortOrder: "asc" },
          },
          listingBroker: true,
        },
      });

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

      // Increment view count (non-blocking, fire-and-forget)
      prisma.property
        .update({
          where: { id },
          data: { viewCount: { increment: 1 } },
        })
        .catch((err) =>
          logger.error("Failed to increment viewCount", {
            propertyId: id,
            error: err instanceof Error ? err.message : "Unknown error",
          }),
        );

      logger.info("Property retrieved", {
        propertyId: property.id,
        userEmail,
      });

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

// ── PUT /api/real-estate/properties/[id] ────────────────────────────

/**
 * PUT /api/real-estate/properties/[id]
 * Updates an existing property
 */
export const PUT = withRateLimit(
  withCSRF(async (request: NextRequest, context: RouteContext) => {
    try {
      const { id } = await context.params;

      // Authenticate
      const userEmail = await getAuthenticatedEmail(request, auth);
      if (!userEmail) {
        logger.warn("Unauthorized property 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);

      // Validate ID
      if (!id || typeof id !== "string" || id.length < 5 || id.length > 50) {
        return ApiResponses.badRequest("Invalid property ID");
      }

      const body = await request.json();

      // ── Convex data-source branch ──────────────────────────────────
      if (getDataSource("real_estate") === "convex") {
        try {
          const { convexMutation } = await import("@/lib/convex-client");

          const updateArgs: Record<string, unknown> = { id: id as any };
          if (body.titleEn !== undefined) updateArgs.titleEn = body.titleEn;
          if (body.titleAr !== undefined) updateArgs.titleAr = body.titleAr;
          if (body.descriptionEn !== undefined)
            updateArgs.descriptionEn = body.descriptionEn;
          if (body.status !== undefined) updateArgs.status = body.status;
          if (body.price !== undefined)
            updateArgs.price = parseFloat(body.price);
          if (body.bedrooms !== undefined)
            updateArgs.bedrooms =
              body.bedrooms != null ? parseInt(body.bedrooms, 10) : undefined;
          if (body.bathrooms !== undefined)
            updateArgs.bathrooms =
              body.bathrooms != null ? parseInt(body.bathrooms, 10) : undefined;

          const propertyId = await convexMutation(
            "real_estate/mutations:updateProperty",
            updateArgs,
          );

          if (propertyId) {
            logger.info("Property updated via Convex", {
              propertyId: id,
              userEmail,
            });
            return NextResponse.json({
              success: true,
              property: { id: propertyId },
            });
          }
        } catch (error) {
          console.error(
            "[Convex fallback] real_estate properties/[id] PUT:",
            error,
          );
          // Fall through to Prisma
        }
      }

      // Verify property exists & belongs to user / org
      const existing = await prisma.property.findFirst({
        where: { id, userId: effectiveUserId },
        select: { id: true, status: true, price: true, builtUpArea: true },
      });
      if (!existing) {
        return ApiResponses.notFound("Property not found");
      }

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

      if (body.titleEn !== undefined) {
        const t = sanitizeText(body.titleEn, 300);
        if (!t || t.length < 3) {
          errors.push("Property title (English) must be at least 3 characters");
        }
      }

      if (
        body.propertyType !== undefined &&
        !isValidEnum(PropertyType, body.propertyType)
      ) {
        errors.push(
          `Invalid property type. Allowed: ${Object.values(PropertyType).join(", ")}`,
        );
      }
      if (
        body.listingType !== undefined &&
        !isValidEnum(ListingType, body.listingType)
      ) {
        errors.push(
          `Invalid listing type. Allowed: ${Object.values(ListingType).join(", ")}`,
        );
      }
      if (
        body.status !== undefined &&
        !isValidEnum(PropertyStatus, body.status)
      ) {
        errors.push(
          `Invalid status. Allowed: ${Object.values(PropertyStatus).join(", ")}`,
        );
      }
      if (
        body.furnishing !== undefined &&
        !isValidEnum(FurnishingType, body.furnishing)
      ) {
        errors.push(
          `Invalid furnishing type. Allowed: ${Object.values(FurnishingType).join(", ")}`,
        );
      }
      if (
        body.ownership !== undefined &&
        !isValidEnum(OwnershipType, body.ownership)
      ) {
        errors.push(
          `Invalid ownership type. Allowed: ${Object.values(OwnershipType).join(", ")}`,
        );
      }
      if (
        body.currency !== undefined &&
        !isValidEnum(GCCCurrencyCode, body.currency)
      ) {
        errors.push(
          `Invalid currency. Allowed: ${Object.values(GCCCurrencyCode).join(", ")}`,
        );
      }

      // Status transition validation
      if (
        body.status !== undefined &&
        isValidEnum(PropertyStatus, body.status)
      ) {
        const currentStatus = existing.status;
        const newStatus = body.status as string;
        const allowedTransitions =
          VALID_STATUS_TRANSITIONS[currentStatus] || [];
        if (
          currentStatus !== newStatus &&
          !allowedTransitions.includes(newStatus)
        ) {
          errors.push(
            `Invalid status transition: ${currentStatus} -> ${newStatus}. Allowed: ${allowedTransitions.join(", ") || "none"}`,
          );
        }
      }

      if (body.price !== undefined) {
        const p = parseFloat(body.price);
        if (isNaN(p) || p < 0) errors.push("Price must be >= 0");
      }
      if (body.city !== undefined) {
        const c = sanitizeText(body.city, 100);
        if (!c || c.length < 2)
          errors.push("City must be at least 2 characters");
      }
      if (body.bedrooms !== undefined && body.bedrooms !== null) {
        const b = parseInt(body.bedrooms, 10);
        if (isNaN(b) || b < 0 || b > 50)
          errors.push("Bedrooms must be between 0 and 50");
      }
      if (body.bathrooms !== undefined && body.bathrooms !== null) {
        const b = parseInt(body.bathrooms, 10);
        if (isNaN(b) || b < 0 || b > 50)
          errors.push("Bathrooms must be between 0 and 50");
      }
      if (body.builtUpArea !== undefined && body.builtUpArea !== null) {
        const a = parseFloat(body.builtUpArea);
        if (isNaN(a) || a < 0)
          errors.push("Built-up area must be a positive number");
      }
      if (body.plotArea !== undefined && body.plotArea !== null) {
        const a = parseFloat(body.plotArea);
        if (isNaN(a) || a < 0)
          errors.push("Plot area must be a positive number");
      }

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

      // ── Build update payload (only provided fields) ─────────────
      const data: Record<string, unknown> = { updatedAt: new Date() };

      // Text fields
      if (body.titleEn !== undefined)
        data.titleEn = sanitizeText(body.titleEn, 300);
      if (body.titleAr !== undefined)
        data.titleAr = sanitizeText(body.titleAr, 300);
      if (body.descriptionEn !== undefined)
        data.descriptionEn = sanitizeText(body.descriptionEn, 5000);
      if (body.descriptionAr !== undefined)
        data.descriptionAr = sanitizeText(body.descriptionAr, 5000);
      if (body.referenceNumber !== undefined)
        data.referenceNumber = sanitizeText(body.referenceNumber, 50);

      // Enums
      if (body.propertyType !== undefined)
        data.propertyType = body.propertyType;
      if (body.listingType !== undefined) data.listingType = body.listingType;
      if (body.status !== undefined) data.status = body.status;
      if (body.furnishing !== undefined) data.furnishing = body.furnishing;
      if (body.ownership !== undefined) data.ownership = body.ownership;
      if (body.currency !== undefined) data.currency = body.currency;

      // Booleans
      if (body.isOffPlan !== undefined)
        data.isOffPlan = body.isOffPlan === true;
      if (body.isNegotiable !== undefined)
        data.isNegotiable = body.isNegotiable !== false;

      // Dates
      if (body.handoverDate !== undefined) {
        data.handoverDate = body.handoverDate
          ? new Date(body.handoverDate)
          : null;
      }

      // Numerics
      if (body.bedrooms !== undefined)
        data.bedrooms =
          body.bedrooms != null ? parseInt(body.bedrooms, 10) : null;
      if (body.bathrooms !== undefined)
        data.bathrooms =
          body.bathrooms != null ? parseInt(body.bathrooms, 10) : null;
      if (body.builtUpArea !== undefined)
        data.builtUpArea =
          body.builtUpArea != null ? parseFloat(body.builtUpArea) : null;
      if (body.plotArea !== undefined)
        data.plotArea =
          body.plotArea != null ? parseFloat(body.plotArea) : null;
      if (body.floor !== undefined)
        data.floor = body.floor != null ? parseInt(body.floor, 10) : null;
      if (body.totalFloors !== undefined)
        data.totalFloors =
          body.totalFloors != null ? parseInt(body.totalFloors, 10) : null;
      if (body.parkingSpaces !== undefined)
        data.parkingSpaces =
          body.parkingSpaces != null ? parseInt(body.parkingSpaces, 10) : null;
      if (body.yearBuilt !== undefined)
        data.yearBuilt =
          body.yearBuilt != null ? parseInt(body.yearBuilt, 10) : null;
      if (body.price !== undefined) data.price = parseFloat(body.price);
      if (body.pricePerSqm !== undefined)
        data.pricePerSqm =
          body.pricePerSqm != null ? parseFloat(body.pricePerSqm) : null;
      if (body.serviceCharges !== undefined)
        data.serviceCharges =
          body.serviceCharges != null ? parseFloat(body.serviceCharges) : null;
      if (body.minContractMonths !== undefined)
        data.minContractMonths =
          body.minContractMonths != null
            ? parseInt(body.minContractMonths, 10)
            : null;

      // Location strings
      if (body.country !== undefined)
        data.country = sanitizeText(body.country, 5) || "QA";
      if (body.city !== undefined) data.city = sanitizeText(body.city, 100);
      if (body.district !== undefined)
        data.district = sanitizeText(body.district, 100);
      if (body.buildingName !== undefined)
        data.buildingName = sanitizeText(body.buildingName, 200);
      if (body.unitNumber !== undefined)
        data.unitNumber = sanitizeText(body.unitNumber, 50);
      if (body.latitude !== undefined)
        data.latitude =
          body.latitude != null ? parseFloat(body.latitude) : null;
      if (body.longitude !== undefined)
        data.longitude =
          body.longitude != null ? parseFloat(body.longitude) : null;
      if (body.mapUrl !== undefined)
        data.mapUrl = body.mapUrl
          ? validator.trim(body.mapUrl).slice(0, 500)
          : null;

      // Rental
      if (body.paymentTerms !== undefined)
        data.paymentTerms = sanitizeText(body.paymentTerms, 200);

      // JSON arrays
      if (body.amenities !== undefined)
        data.amenities = Array.isArray(body.amenities) ? body.amenities : [];
      if (body.highlights !== undefined)
        data.highlights = Array.isArray(body.highlights) ? body.highlights : [];

      // Access
      if (body.keyLocation !== undefined)
        data.keyLocation = sanitizeText(body.keyLocation, 200);
      if (body.accessNotes !== undefined)
        data.accessNotes = sanitizeText(body.accessNotes, 500);
      if (body.viewingHoursNote !== undefined)
        data.viewingHoursNote = sanitizeText(body.viewingHoursNote, 200);

      // Broker
      if (body.listingBrokerId !== undefined)
        data.listingBrokerId = body.listingBrokerId || null;

      // ── Recalculate pricePerSqm if price or area changed ────────
      const finalPrice =
        data.price !== undefined ? (data.price as number) : existing.price;
      const finalArea =
        data.builtUpArea !== undefined
          ? (data.builtUpArea as number | null)
          : existing.builtUpArea;

      if (
        (data.price !== undefined || data.builtUpArea !== undefined) &&
        finalArea &&
        finalArea > 0 &&
        finalPrice > 0
      ) {
        data.pricePerSqm = Math.round((finalPrice / finalArea) * 100) / 100;
      }

      // If status transitions to SOLD or RENTED, record the timestamp
      if (body.status === "SOLD" || body.status === "RENTED") {
        data.soldRentedAt = new Date();
      }

      const property = await prisma.property.update({
        where: { id },
        data,
        include: {
          images: { orderBy: { sortOrder: "asc" } },
        },
      });

      logger.info("Property updated", {
        propertyId: property.id,
        userEmail,
      });

      // Non-blocking: trigger notifications for price changes
      if (
        existing &&
        body.price !== undefined &&
        body.price !== existing.price
      ) {
        handlePriceChange(id, existing.price, body.price).catch((err) =>
          logger.error("Non-blocking handlePriceChange failed", {
            error: getErrorMessage(err),
          }),
        );
      }

      // Non-blocking: trigger notifications for status changes
      if (existing && body.status && body.status !== existing.status) {
        handleStatusChange(
          id,
          existing.status,
          body.status,
          effectiveUserId,
        ).catch((err) =>
          logger.error("Non-blocking handleStatusChange failed", {
            error: getErrorMessage(err),
          }),
        );
      }

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

// ── DELETE /api/real-estate/properties/[id] ─────────────────────────

/**
 * DELETE /api/real-estate/properties/[id]
 * Archives a property by setting status to OFF_MARKET.
 * Blocked if property has active (REQUESTED / CONFIRMED / IN_PROGRESS) viewings.
 */
export const DELETE = withRateLimit(
  withCSRF(async (request: NextRequest, context: RouteContext) => {
    try {
      const { id } = await context.params;

      // Authenticate
      const userEmail = await getAuthenticatedEmail(request, auth);
      if (!userEmail) {
        logger.warn("Unauthorized property delete 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);

      // Validate ID
      if (!id || typeof id !== "string" || id.length < 5 || id.length > 50) {
        return ApiResponses.badRequest("Invalid property ID");
      }

      // ── Convex data-source branch ──────────────────────────────────
      if (getDataSource("real_estate") === "convex") {
        try {
          const { convexMutation } = await import("@/lib/convex-client");

          const propertyId = await convexMutation(
            "real_estate/mutations:updateProperty",
            { id: id as any, status: "OFF_MARKET" },
          );

          if (propertyId) {
            logger.info("Property archived via Convex", {
              propertyId: id,
              userEmail,
            });
            return NextResponse.json({
              success: true,
              message: "Property archived successfully",
              property: { id: propertyId, status: "OFF_MARKET" },
            });
          }
        } catch (error) {
          console.error(
            "[Convex fallback] real_estate properties/[id] DELETE:",
            error,
          );
          // Fall through to Prisma
        }
      }

      // Verify property exists & belongs to user / org
      const existing = await prisma.property.findFirst({
        where: { id, userId: effectiveUserId },
        select: { id: true, status: true },
      });
      if (!existing) {
        return ApiResponses.notFound("Property not found");
      }

      // Check for active viewings
      const activeViewings = await prisma.propertyViewing.count({
        where: {
          propertyId: id,
          status: { in: ["REQUESTED", "CONFIRMED", "IN_PROGRESS"] },
        },
      });

      if (activeViewings > 0) {
        return NextResponse.json(
          {
            success: false,
            error: `Cannot archive property: ${activeViewings} active viewing(s) exist. Cancel or complete them first.`,
            errorCode: "ACTIVE_VIEWINGS_EXIST",
            activeViewings,
          },
          { status: 409 },
        );
      }

      // Soft-delete by setting status to OFF_MARKET
      const property = await prisma.property.update({
        where: { id },
        data: {
          status: "OFF_MARKET",
          updatedAt: new Date(),
        },
      });

      logger.info("Property archived", {
        propertyId: property.id,
        userEmail,
        previousStatus: existing.status,
      });

      return NextResponse.json({
        success: true,
        message: "Property archived successfully",
        property: { id: property.id, status: property.status },
      });
    } catch (error) {
      logger.error("Failed to archive property", {
        error: getErrorMessage(error),
      });
      return ApiResponses.serverError("Failed to archive property");
    }
  }),
  rateLimiters.authGeneral,
);
