/**
 * Real Estate Property Search API
 * POST - Advanced search endpoint designed for AI agent use
 *
 * Accepts structured criteria and returns top matches sorted by relevance.
 * 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 ListingType = { SALE: "SALE", RENT: "RENT" } as const;
type ListingType = (typeof ListingType)[keyof typeof ListingType];

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 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 { 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 { getDataSource } from "@/lib/data-source";

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);
}

/**
 * Compute a simple relevance score (0-100) for a property against the criteria.
 * Higher = better match.
 */
function computeRelevanceScore(
  property: {
    listingType: string;
    propertyType: string;
    price: number;
    currency: string;
    bedrooms: number | null;
    city: string;
    district: string | null;
  },
  criteria: {
    listingType?: string;
    propertyType?: string;
    maxBudget?: number;
    minBedrooms?: number;
    area?: string;
    currency?: string;
  },
): number {
  let score = 50; // baseline

  // Listing type match (exact)
  if (criteria.listingType && property.listingType === criteria.listingType) {
    score += 15;
  }

  // Property type match (exact)
  if (
    criteria.propertyType &&
    property.propertyType === criteria.propertyType
  ) {
    score += 15;
  }

  // Budget: the further under budget, the better (up to +10)
  if (criteria.maxBudget && criteria.maxBudget > 0) {
    if (property.price <= criteria.maxBudget) {
      const ratio = property.price / criteria.maxBudget;
      // 0.7-1.0 of budget = best range
      if (ratio >= 0.7) {
        score += 10;
      } else if (ratio >= 0.4) {
        score += 7;
      } else {
        score += 3;
      }
    }
    // over-budget: penalise
    if (property.price > criteria.maxBudget) {
      const overBy = (property.price - criteria.maxBudget) / criteria.maxBudget;
      score -= Math.min(20, Math.round(overBy * 30));
    }
  }

  // Bedrooms meet minimum
  if (
    criteria.minBedrooms &&
    property.bedrooms !== null &&
    property.bedrooms >= criteria.minBedrooms
  ) {
    score += 10;
  }

  // Area match (city or district contains keyword)
  if (criteria.area) {
    const areaLower = criteria.area.toLowerCase();
    if (property.city.toLowerCase().includes(areaLower)) {
      score += 10;
    } else if (property.district?.toLowerCase().includes(areaLower)) {
      score += 8;
    }
  }

  // Currency match bonus
  if (criteria.currency && property.currency === criteria.currency) {
    score += 5;
  }

  return Math.max(0, Math.min(100, score));
}

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

/**
 * POST /api/real-estate/properties/search
 *
 * Body:
 *   - listingType   (ListingType)       SALE | RENT
 *   - propertyType  (PropertyType)      e.g. APARTMENT, VILLA
 *   - minBedrooms   (number)            minimum bedrooms
 *   - maxBudget     (number)            maximum price
 *   - area          (string)            city or district keyword
 *   - currency      (GCCCurrencyCode)   e.g. QAR, AED
 *   - limit         (number)            max results (default 10)
 *
 * Returns top matches sorted by relevance score descending.
 */
export const POST = withRateLimit(
  withCSRF(async (request: NextRequest) => {
    try {
      // Authenticate
      const userEmail = await getAuthenticatedEmail(request, auth);
      if (!userEmail) {
        logger.warn("Unauthorized property search 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 body = await request.json();

      // ── Validate criteria ───────────────────────────────────────
      const errors: string[] = [];

      if (body.listingType && !isValidEnum(ListingType, body.listingType)) {
        errors.push(
          `Invalid listing type. Allowed: ${Object.values(ListingType).join(", ")}`,
        );
      }
      if (body.propertyType && !isValidEnum(PropertyType, body.propertyType)) {
        errors.push(
          `Invalid property type. Allowed: ${Object.values(PropertyType).join(", ")}`,
        );
      }
      if (body.currency && !isValidEnum(GCCCurrencyCode, body.currency)) {
        errors.push(
          `Invalid currency. Allowed: ${Object.values(GCCCurrencyCode).join(", ")}`,
        );
      }
      if (body.maxBudget !== undefined) {
        const b = parseFloat(body.maxBudget);
        if (isNaN(b) || b < 0)
          errors.push("maxBudget must be a positive number");
      }
      if (body.minBedrooms !== undefined) {
        const b = parseInt(body.minBedrooms, 10);
        if (isNaN(b) || b < 0)
          errors.push("minBedrooms must be a non-negative integer");
      }

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

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

          const results = await convexQuery(
            "real_estate/queries:searchProperties",
            {
              userId: effectiveUserId,
              listingType: body.listingType,
              propertyType: body.propertyType,
              minBedrooms:
                body.minBedrooms != null
                  ? parseInt(body.minBedrooms, 10)
                  : undefined,
              maxBudget:
                body.maxBudget != null ? parseFloat(body.maxBudget) : undefined,
              area: body.area,
              currency: body.currency,
              limit: body.limit || 10,
            },
          );

          if (results && Array.isArray(results)) {
            logger.info("Property search executed via Convex", {
              userEmail,
              returnedCount: results.length,
            });
            return NextResponse.json({
              success: true,
              properties: results,
              total: results.length,
              candidateCount: results.length,
            });
          }
        } catch (error) {
          console.error(
            "[Convex fallback] real_estate properties/search:",
            error,
          );
          // Fall through to Prisma
        }
      }

      // ── Build database filter ───────────────────────────────────
      const where: Record<string, unknown> = {
        userId: effectiveUserId,
        status: { in: ["AVAILABLE", "COMING_SOON"] as PropertyStatus[] },
      };

      if (body.listingType) {
        where.listingType = body.listingType;
      }
      if (body.propertyType) {
        where.propertyType = body.propertyType;
      }
      if (body.currency) {
        where.currency = body.currency;
      }

      // Bedrooms >= minBedrooms
      if (body.minBedrooms !== undefined) {
        where.bedrooms = { gte: parseInt(body.minBedrooms, 10) };
      }

      // Price <= maxBudget (with 10% flexibility to allow slight over-budget matches)
      if (body.maxBudget !== undefined) {
        const budget = parseFloat(body.maxBudget);
        where.price = { lte: budget * 1.1 };
      }

      // Area (city OR district)
      if (
        body.area &&
        typeof body.area === "string" &&
        body.area.trim().length > 0
      ) {
        const areaKeyword = body.area.trim();
        where.OR = [
          { city: { contains: areaKeyword, mode: "insensitive" } },
          { district: { contains: areaKeyword, mode: "insensitive" } },
        ];
      }

      // Limit results
      const limit = clampPaginationLimit(body.limit || 10);

      // Fetch a broader set so we can score & sort in-memory
      const fetchLimit = Math.min(limit * 3, 300);

      const candidates = await prisma.property.findMany({
        where,
        include: {
          images: {
            where: { isPrimary: true },
            take: 1,
          },
        },
        orderBy: { listedAt: "desc" },
        take: fetchLimit,
      });

      // ── Score & rank ────────────────────────────────────────────
      const criteria = {
        listingType: body.listingType,
        propertyType: body.propertyType,
        maxBudget:
          body.maxBudget != null ? parseFloat(body.maxBudget) : undefined,
        minBedrooms:
          body.minBedrooms != null ? parseInt(body.minBedrooms, 10) : undefined,
        area: body.area,
        currency: body.currency,
      };

      const scored = candidates
        .map((property) => ({
          ...property,
          relevanceScore: computeRelevanceScore(
            {
              listingType: property.listingType,
              propertyType: property.propertyType,
              price: property.price,
              currency: property.currency,
              bedrooms: property.bedrooms,
              city: property.city,
              district: property.district,
            },
            criteria,
          ),
        }))
        .sort((a, b) => b.relevanceScore - a.relevanceScore)
        .slice(0, limit);

      logger.info("Property search executed", {
        userEmail,
        criteria: {
          listingType: body.listingType,
          propertyType: body.propertyType,
          maxBudget: body.maxBudget,
          minBedrooms: body.minBedrooms,
          area: body.area,
        },
        candidateCount: candidates.length,
        returnedCount: scored.length,
      });

      return NextResponse.json({
        success: true,
        properties: scored,
        total: scored.length,
        candidateCount: candidates.length,
      });
    } catch (error) {
      logger.error("Property search error", {
        error: getErrorMessage(error),
      });
      return ApiResponses.serverError("Failed to search properties");
    }
  }),
  rateLimiters.authGeneral,
);
