/**
 * Real Estate Properties API
 * GET  - List properties with filters (type, status, city, district, price range, bedrooms)
 * POST - Create a new property listing
 *
 * 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 { createId } from "@paralleldrive/cuid2";
import { clampPaginationLimit } from "@/lib/config/constants";
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 validator from "validator";
import { handleNewListing } from "@/lib/services/real-estate/property-update-triggers.service";
export const dynamic = "force-dynamic";

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

/** Validate that a value belongs to a TypeScript string-enum */
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);
}

/** Sanitise a free-text string: trim, collapse whitespace, escape HTML */
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);
}

// ── GET /api/real-estate/properties ─────────────────────────────────

/**
 * GET /api/real-estate/properties
 * Returns paginated property listings for the authenticated user / org
 *
 * Query parameters:
 *   - limit        (number)  max results per page
 *   - offset       (number)  skip N records
 *   - status       (PropertyStatus)  filter by listing status
 *   - propertyType (PropertyType)    filter by property type
 *   - listingType  (ListingType)     filter SALE | RENT
 *   - city         (string)          filter by city
 *   - district     (string)          filter by district
 *   - minPrice     (number)          lower price bound
 *   - maxPrice     (number)          upper price bound
 *   - bedrooms     (number)          exact bedroom count
 *   - sort         (string)          sort field (price | createdAt | listedAt)
 *   - order        (asc | desc)      sort direction
 */
export const GET = withRateLimit(async (request: NextRequest) => {
  try {
    // Authenticate
    const userEmail = await getAuthenticatedEmail(request, auth);
    if (!userEmail) {
      logger.warn("Unauthorized properties 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);

    // Parse query params
    const sp = request.nextUrl.searchParams;
    const limit = clampPaginationLimit(parseInt(sp.get("limit") || "20", 10));
    const offset = Math.max(0, parseInt(sp.get("offset") || "0", 10));

    // Build where clause
    const where: Record<string, unknown> = { userId: effectiveUserId };
    const status = sp.get("status");
    if (
      status &&
      Object.values(PropertyStatus).includes(status as PropertyStatus)
    ) {
      where.status = status;
    }
    const propertyType = sp.get("propertyType");
    if (
      propertyType &&
      Object.values(PropertyType).includes(propertyType as PropertyType)
    ) {
      where.propertyType = propertyType;
    }
    const listingType = sp.get("listingType");
    if (
      listingType &&
      Object.values(ListingType).includes(listingType as ListingType)
    ) {
      where.listingType = listingType;
    }
    const cityParam = sp.get("city");
    if (cityParam) {
      where.city = { contains: cityParam, mode: "insensitive" };
    }
    const district = sp.get("district");
    if (district) {
      where.district = { contains: district, mode: "insensitive" };
    }
    const minPrice = sp.get("minPrice");
    const maxPrice = sp.get("maxPrice");
    if (minPrice || maxPrice) {
      const priceFilter: Record<string, number> = {};
      if (minPrice) priceFilter.gte = parseFloat(minPrice);
      if (maxPrice) priceFilter.lte = parseFloat(maxPrice);
      where.price = priceFilter;
    }
    const bedrooms = sp.get("bedrooms");
    if (bedrooms) {
      where.bedrooms = parseInt(bedrooms, 10);
    }

    // Sort
    const sortField = sp.get("sort") || "createdAt";
    const sortOrder = sp.get("order") === "asc" ? "asc" : "desc";
    const orderBy: Record<string, string> = {};
    if (["price", "createdAt", "listedAt"].includes(sortField)) {
      orderBy[sortField] = sortOrder;
    } else {
      orderBy.createdAt = sortOrder;
    }

    const [properties, total] = await Promise.all([
      prisma.property.findMany({
        where,
        include: { images: true },
        orderBy,
        take: limit,
        skip: offset,
      }),
      prisma.property.count({ where }),
    ]);

    return NextResponse.json({
      success: true,
      properties,
      total,
      limit,
      offset,
    });
  } catch (error) {
    logger.error("Properties list error", {
      error: getErrorMessage(error),
    });
    return ApiResponses.serverError("Failed to retrieve properties");
  }
}, rateLimiters.authGeneral);

// ── POST /api/real-estate/properties ────────────────────────────────

/**
 * POST /api/real-estate/properties
 * Creates a new property listing
 */
export const POST = withRateLimit(
  withCSRF(async (request: NextRequest) => {
    try {
      // Authenticate
      const userEmail = await getAuthenticatedEmail(request, auth);
      if (!userEmail) {
        logger.warn("Unauthorized property 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);

      // Parse body
      const body = await request.json();

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

      // Required: titleEn
      const titleEn = sanitizeText(body.titleEn, 300);
      if (!titleEn || titleEn.length < 3) {
        errors.push(
          "Property title (English) is required and must be at least 3 characters",
        );
      }

      // Required: propertyType
      if (!isValidEnum(PropertyType, body.propertyType)) {
        errors.push(
          `Invalid property type. Allowed: ${Object.values(PropertyType).join(", ")}`,
        );
      }

      // Required: listingType
      if (!isValidEnum(ListingType, body.listingType)) {
        errors.push(
          `Invalid listing type. Allowed: ${Object.values(ListingType).join(", ")}`,
        );
      }

      // Required: city
      const city = sanitizeText(body.city, 100);
      if (!city || city.length < 2) {
        errors.push("City is required");
      }

      // Required: price
      const price = parseFloat(body.price);
      if (isNaN(price) || price < 0) {
        errors.push("A valid price is required (>= 0)");
      }

      // Optional enums – validate if provided
      if (body.status && !isValidEnum(PropertyStatus, body.status)) {
        errors.push(
          `Invalid status. Allowed: ${Object.values(PropertyStatus).join(", ")}`,
        );
      }
      if (body.furnishing && !isValidEnum(FurnishingType, body.furnishing)) {
        errors.push(
          `Invalid furnishing type. Allowed: ${Object.values(FurnishingType).join(", ")}`,
        );
      }
      if (body.ownership && !isValidEnum(OwnershipType, body.ownership)) {
        errors.push(
          `Invalid ownership type. Allowed: ${Object.values(OwnershipType).join(", ")}`,
        );
      }
      if (body.currency && !isValidEnum(GCCCurrencyCode, body.currency)) {
        errors.push(
          `Invalid currency. Allowed: ${Object.values(GCCCurrencyCode).join(", ")}`,
        );
      }

      // Numeric range guards
      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 },
        );
      }

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

      // ── Duplicate check (buildingName + unitNumber) ─────────────
      const buildingName = sanitizeText(body.buildingName, 200);
      const unitNumber = sanitizeText(body.unitNumber, 50);

      if (buildingName && unitNumber) {
        const duplicate = await prisma.property.findFirst({
          where: {
            userId: effectiveUserId,
            buildingName,
            unitNumber,
            status: { not: "OFF_MARKET" },
          },
          select: { id: true, referenceNumber: true },
        });

        if (duplicate) {
          return NextResponse.json(
            {
              success: false,
              error: `A property already exists at ${buildingName}, unit ${unitNumber} (ref: ${duplicate.referenceNumber || duplicate.id})`,
              errorCode: "DUPLICATE_PROPERTY",
              existingPropertyId: duplicate.id,
            },
            { status: 409 },
          );
        }
      }

      // ── Auto-generate reference number ──────────────────────────
      const existingCount = await prisma.property.count({
        where: { userId: effectiveUserId },
      });
      const refPrefix = body.listingType === "RENT" ? "R" : "S";
      const autoRef = `PROP-${refPrefix}-${String(existingCount + 1).padStart(5, "0")}`;
      const referenceNumber = sanitizeText(body.referenceNumber, 50) || autoRef;

      // ── Auto-calculate pricePerSqm ──────────────────────────────
      const builtUpArea =
        body.builtUpArea != null ? parseFloat(body.builtUpArea) : null;
      let pricePerSqm: number | null =
        body.pricePerSqm != null ? parseFloat(body.pricePerSqm) : null;
      if (
        pricePerSqm === null &&
        builtUpArea &&
        builtUpArea > 0 &&
        !isNaN(price)
      ) {
        pricePerSqm = Math.round((price / builtUpArea) * 100) / 100;
      }

      // ── Create property ─────────────────────────────────────────
      const propertyId = createId();

      const property = await prisma.property.create({
        data: {
          id: propertyId,
          userId: effectiveUserId,
          organizationId: organization?.id || null,

          // Basic info
          titleEn: titleEn!,
          titleAr: sanitizeText(body.titleAr, 300),
          descriptionEn: sanitizeText(body.descriptionEn, 5000),
          descriptionAr: sanitizeText(body.descriptionAr, 5000),
          referenceNumber,

          // Classification
          propertyType: body.propertyType as PropertyType,
          listingType: body.listingType as ListingType,
          status: (body.status as PropertyStatus) || "AVAILABLE",
          furnishing: (body.furnishing as FurnishingType) || "UNFURNISHED",
          ownership: (body.ownership as OwnershipType) || "FREEHOLD",
          isOffPlan: body.isOffPlan === true,
          handoverDate: body.handoverDate ? new Date(body.handoverDate) : null,

          // Specs
          bedrooms: body.bedrooms != null ? parseInt(body.bedrooms, 10) : null,
          bathrooms:
            body.bathrooms != null ? parseInt(body.bathrooms, 10) : null,
          builtUpArea,
          plotArea: body.plotArea != null ? parseFloat(body.plotArea) : null,
          floor: body.floor != null ? parseInt(body.floor, 10) : null,
          totalFloors:
            body.totalFloors != null ? parseInt(body.totalFloors, 10) : null,
          parkingSpaces:
            body.parkingSpaces != null
              ? parseInt(body.parkingSpaces, 10)
              : null,
          yearBuilt:
            body.yearBuilt != null ? parseInt(body.yearBuilt, 10) : null,

          // Location
          country: sanitizeText(body.country, 5) || "QA",
          city: city!,
          district: sanitizeText(body.district, 100),
          streetAddress: sanitizeText(body.streetAddress, 300),
          buildingName,
          unitNumber,
          latitude: body.latitude != null ? parseFloat(body.latitude) : null,
          longitude: body.longitude != null ? parseFloat(body.longitude) : null,
          mapUrl: body.mapUrl
            ? validator.trim(body.mapUrl).slice(0, 500)
            : null,

          // Pricing
          price,
          currency: (body.currency as GCCCurrencyCode) || "QAR",
          pricePerSqm,
          isNegotiable: body.isNegotiable !== false,
          serviceCharges:
            body.serviceCharges != null
              ? parseFloat(body.serviceCharges)
              : null,

          // Rental specific
          minContractMonths:
            body.minContractMonths != null
              ? parseInt(body.minContractMonths, 10)
              : null,
          paymentTerms: sanitizeText(body.paymentTerms, 200),

          // Features
          amenities: Array.isArray(body.amenities) ? body.amenities : [],
          highlights: Array.isArray(body.highlights) ? body.highlights : [],

          // Access
          keyLocation: sanitizeText(body.keyLocation, 200),
          accessNotes: sanitizeText(body.accessNotes, 500),
          viewingHoursNote: sanitizeText(body.viewingHoursNote, 200),

          // Broker assignment
          listingBrokerId: body.listingBrokerId || null,

          updatedAt: new Date(),
        },
        include: {
          images: true,
        },
      });

      logger.info("Property created", {
        propertyId: property.id,
        userEmail,
        titleEn: property.titleEn,
        propertyType: property.propertyType,
        listingType: property.listingType,
      });

      // Non-blocking: run matching for new listing
      handleNewListing(property.id, effectiveUserId).catch((err) =>
        logger.error("Non-blocking handleNewListing failed", {
          error: getErrorMessage(err),
        }),
      );

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