/**
 * Property Matching API
 * GET - Get matching properties for a client requirement set
 */

import { NextRequest } 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 { ApiResponses } from "@/lib/utils/api-response.util";
import { withRateLimit, rateLimiters } from "@/lib/rate-limiter";
import { getErrorMessage } from "@/lib/errors";
import { getDataSource } from "@/lib/data-source";

export const dynamic = "force-dynamic";

/**
 * GET /api/real-estate/matches/[clientId]
 * Get matching properties for a client. Run matching algorithm inline.
 * Score: price 30%, bedrooms 20%, area 25%, amenities 15%, type 10%
 */
export const GET = withRateLimit(
  async (
    request: NextRequest,
    { params }: { params: Promise<{ clientId: string }> },
  ) => {
    try {
      const { clientId } = await params;

      const userEmail = await getAuthenticatedEmail(request, auth);
      if (!userEmail) {
        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 convexResult = await convexQuery(
            "real_estate/queries:getMatches",
            { clientId, userId: effectiveUserId },
          );
          if (convexResult) {
            const typed = convexResult as { matches: unknown[] };
            return ApiResponses.success({
              clientId,
              totalAvailable: typed.matches.length,
              matches: typed.matches,
            });
          }
          return ApiResponses.notFound("Client requirements not found");
        } catch (convexErr) {
          console.error(
            "[Convex fallback] real_estate matches GET:",
            convexErr,
          );
        }
      }

      // Fetch client requirements
      const requirements = await prisma.clientRequirements.findFirst({
        where: { id: clientId, userId: effectiveUserId },
      });
      if (!requirements) {
        return ApiResponses.notFound("Client requirements not found");
      }

      // Fetch available properties for matching
      const properties = await prisma.property.findMany({
        where: {
          userId: effectiveUserId,
          status: "AVAILABLE",
          ...(requirements.listingType
            ? { listingType: requirements.listingType }
            : {}),
        },
        include: {
          images: {
            where: { isPrimary: true },
            take: 1,
            select: { url: true },
          },
        },
      });

      const reqTypes = (requirements.propertyTypes as string[]) || [];
      const reqAreas = (requirements.preferredAreas as string[]) || [];
      const reqMustHaves = (requirements.mustHaves as string[]) || [];

      // Score each property
      const scored: Array<{
        property: (typeof properties)[number];
        score: number;
        breakdown: Record<string, number>;
        reasons: string[];
      }> = [];

      for (const prop of properties) {
        let score = 0;
        const breakdown: Record<string, number> = {};
        const reasons: string[] = [];

        // --- Price (30%) ---
        let priceScore = 0;
        if (
          requirements.minBudget !== null ||
          requirements.maxBudget !== null
        ) {
          const inRange =
            (requirements.minBudget === null ||
              prop.price >= requirements.minBudget) &&
            (requirements.maxBudget === null ||
              prop.price <= requirements.maxBudget);
          if (inRange) {
            priceScore = 30;
            reasons.push("Price within budget");
          } else if (
            requirements.maxBudget &&
            prop.price <= requirements.maxBudget * 1.1
          ) {
            priceScore = 20;
            reasons.push("Price slightly over budget (within 10%)");
          } else if (
            requirements.maxBudget &&
            prop.price <= requirements.maxBudget * 1.25
          ) {
            priceScore = 10;
            reasons.push("Price moderately over budget (within 25%)");
          }
        } else {
          priceScore = 15;
        }
        breakdown.price = priceScore;
        score += priceScore;

        // --- Bedrooms (20%) ---
        let bedroomScore = 0;
        if (
          prop.bedrooms !== null &&
          (requirements.minBedrooms !== null ||
            requirements.maxBedrooms !== null)
        ) {
          const minOk =
            requirements.minBedrooms === null ||
            prop.bedrooms >= requirements.minBedrooms;
          const maxOk =
            requirements.maxBedrooms === null ||
            prop.bedrooms <= requirements.maxBedrooms;
          if (minOk && maxOk) {
            bedroomScore = 20;
            reasons.push(`${prop.bedrooms} bedrooms matches`);
          } else if (
            (requirements.minBedrooms !== null &&
              prop.bedrooms === requirements.minBedrooms - 1) ||
            (requirements.maxBedrooms !== null &&
              prop.bedrooms === requirements.maxBedrooms + 1)
          ) {
            bedroomScore = 10;
            reasons.push("Bedrooms close to range");
          }
        } else {
          bedroomScore = 10;
        }
        breakdown.bedrooms = bedroomScore;
        score += bedroomScore;

        // --- Area/Location (25%) ---
        let areaScore = 0;
        if (reqAreas.length > 0) {
          if (prop.district && reqAreas.includes(prop.district)) {
            areaScore = 25;
            reasons.push(`Preferred district: ${prop.district}`);
          } else if (prop.city && reqAreas.includes(prop.city)) {
            areaScore = 15;
            reasons.push(`Preferred city: ${prop.city}`);
          }
        } else {
          areaScore = 12;
        }
        breakdown.area = areaScore;
        score += areaScore;

        // --- Amenities/Must-haves (15%) ---
        let amenityScore = 0;
        if (reqMustHaves.length > 0) {
          const propAmenities = (prop.amenities as string[]) || [];
          const matched = reqMustHaves.filter((a: string) =>
            propAmenities.some((pa: string) =>
              pa.toLowerCase().includes(a.toLowerCase()),
            ),
          );
          amenityScore = Math.min(
            15,
            Math.round((matched.length / reqMustHaves.length) * 15),
          );
          if (matched.length > 0) {
            reasons.push(
              `${matched.length}/${reqMustHaves.length} must-haves found`,
            );
          }
        } else {
          amenityScore = 7;
        }
        breakdown.amenities = amenityScore;
        score += amenityScore;

        // --- Property Type (10%) ---
        let typeScore = 0;
        if (reqTypes.length > 0) {
          if (reqTypes.includes(prop.propertyType)) {
            typeScore = 10;
            reasons.push(`Type: ${prop.propertyType}`);
          }
        } else {
          typeScore = 5;
        }
        breakdown.type = typeScore;
        score += typeScore;

        scored.push({ property: prop, score, breakdown, reasons });
      }

      // Sort descending by score
      scored.sort((a, b) => b.score - a.score);

      // Upsert PropertyMatch records
      const upsertPromises = scored.slice(0, 50).map(async (match) => {
        try {
          await prisma.propertyMatch.upsert({
            where: {
              propertyId_requirementId: {
                propertyId: match.property.id,
                requirementId: clientId,
              },
            },
            update: {
              matchScore: match.score,
              matchReasons: match.reasons,
            },
            create: {
              id: createId(),
              propertyId: match.property.id,
              requirementId: clientId,
              matchScore: match.score,
              matchReasons: match.reasons,
            },
          });
        } catch {
          // Non-fatal upsert error
        }
      });
      await Promise.all(upsertPromises);

      // Return top 20 matches
      const topMatches = scored.slice(0, 20).map((m) => ({
        propertyId: m.property.id,
        titleEn: m.property.titleEn,
        titleAr: m.property.titleAr,
        propertyType: m.property.propertyType,
        listingType: m.property.listingType,
        price: m.property.price,
        currency: m.property.currency,
        bedrooms: m.property.bedrooms,
        bathrooms: m.property.bathrooms,
        builtUpArea: m.property.builtUpArea,
        city: m.property.city,
        district: m.property.district,
        imageUrl: m.property.images[0]?.url || null,
        score: m.score,
        maxScore: 100,
        breakdown: m.breakdown,
        reasons: m.reasons,
      }));

      logger.info("[Matches API] Matching completed", {
        clientId,
        totalProperties: properties.length,
        matchesReturned: topMatches.length,
      });

      return ApiResponses.success({
        clientId,
        totalAvailable: properties.length,
        matches: topMatches,
      });
    } catch (error) {
      logger.error("[Matches API] GET error", {
        error: getErrorMessage(error),
      });
      return ApiResponses.serverError("Failed to compute matches");
    }
  },
  rateLimiters.authGeneral,
);
