import "server-only";

import { LicitationCategory } from "@prisma/client";
import { prisma } from "@/lib/prisma";
import type { RecommendationResultItem } from "@/lib/licitations/types";
import { toStringArray } from "@/lib/licitations/utils";

type ParsedProfile = {
  id: string;
  locations: {
    stateCodes: string[];
    municipalityCodes: string[];
  };
  categoriesSupported: string[];
  keywords: string[];
  minAmount: number | null;
  maxAmount: number | null;
};

function parseLocations(value: unknown) {
  if (!value || typeof value !== "object") {
    return {
      stateCodes: [] as string[],
      municipalityCodes: [] as string[],
    };
  }

  const stateCodes = toStringArray((value as Record<string, unknown>).stateCodes);
  const municipalityCodes = toStringArray((value as Record<string, unknown>).municipalityCodes);

  return {
    stateCodes,
    municipalityCodes,
  };
}

function parseProfile(profile: {
  id: string;
  locations: unknown;
  categoriesSupported: unknown;
  keywords: unknown;
  minAmount: unknown;
  maxAmount: unknown;
}): ParsedProfile {
  return {
    id: profile.id,
    locations: parseLocations(profile.locations),
    categoriesSupported: toStringArray(profile.categoriesSupported).map((item) => item.toUpperCase()),
    keywords: toStringArray(profile.keywords).map((item) => item.toLowerCase()),
    minAmount: profile.minAmount == null ? null : Number(profile.minAmount),
    maxAmount: profile.maxAmount == null ? null : Number(profile.maxAmount),
  };
}

function hasAmountMatch(profile: ParsedProfile, amount: number | null) {
  if (amount == null) {
    return false;
  }

  if (profile.minAmount != null && amount < profile.minAmount) {
    return false;
  }

  if (profile.maxAmount != null && amount > profile.maxAmount) {
    return false;
  }

  return true;
}

function keywordOverlap(profile: ParsedProfile, text: string) {
  if (!profile.keywords.length) {
    return [] as string[];
  }

  const normalized = text.toLowerCase();

  return profile.keywords.filter((keyword) => normalized.includes(keyword));
}

function normalizeCategory(category: LicitationCategory | null) {
  if (!category) {
    return null;
  }

  return category.toUpperCase();
}

async function ensureCompanyProfileForUser(userId: string) {
  const existing = await prisma.companyProfile.findUnique({
    where: { userId },
    select: {
      id: true,
      locations: true,
      categoriesSupported: true,
      keywords: true,
      minAmount: true,
      maxAmount: true,
    },
  });

  if (existing) {
    return existing;
  }

  const organization = await prisma.organization.findUnique({
    where: { userId },
    select: {
      id: true,
      operatingState: true,
      municipality: true,
      industry: true,
      businessPurpose: true,
    },
  });

  const keywords = [organization?.industry, organization?.businessPurpose]
    .filter((value): value is string => Boolean(value && value.trim()))
    .flatMap((value) => value.split(/[,;]/g).map((entry) => entry.trim().toLowerCase()))
    .filter(Boolean)
    .slice(0, 20);

  return prisma.companyProfile.create({
    data: {
      userId,
      organizationId: organization?.id,
      locations: {
        stateCodes: organization?.operatingState ? [organization.operatingState] : [],
        municipalityCodes: organization?.municipality ? [organization.municipality] : [],
      },
      categoriesSupported: [LicitationCategory.UNKNOWN],
      keywords,
      minAmount: null,
      maxAmount: null,
    },
    select: {
      id: true,
      locations: true,
      categoriesSupported: true,
      keywords: true,
      minAmount: true,
      maxAmount: true,
    },
  });
}

export async function getLicitationRecommendationsForUser(userId: string, profileId?: string | null) {
  const rawProfile = profileId
    ? await prisma.companyProfile.findFirst({
        where: {
          id: profileId,
          userId,
        },
        select: {
          id: true,
          locations: true,
          categoriesSupported: true,
          keywords: true,
          minAmount: true,
          maxAmount: true,
        },
      })
    : await ensureCompanyProfileForUser(userId);

  if (!rawProfile) {
    return {
      profileId: null,
      results: [] as RecommendationResultItem[],
    };
  }

  const profile = parseProfile(rawProfile);

  const records = await prisma.licitation.findMany({
    where: {
      OR: [{ isOpen: true }, { closingDate: { gte: new Date() } }],
      municipality: {
        isActive: true,
      },
    },
    orderBy: [{ publishDate: "desc" }, { createdAt: "desc" }],
    take: 200,
    include: {
      municipality: {
        select: {
          stateCode: true,
          stateName: true,
          municipalityCode: true,
          municipalityName: true,
        },
      },
    },
  });

  const scored = records.map((record) => {
    let score = 0;
    const reasons: string[] = [];

    if (
      profile.locations.stateCodes.includes(record.municipality.stateCode) ||
      profile.locations.municipalityCodes.includes(record.municipality.municipalityCode)
    ) {
      score += 35;
      reasons.push("Coincide con tu ubicacion objetivo.");
    }

    const category = normalizeCategory(record.category);

    if (category && profile.categoriesSupported.includes(category)) {
      score += 25;
      reasons.push("Coincide con tus categorias de oferta.");
    }

    const amountValue = record.amount == null ? null : Number(record.amount);

    if (hasAmountMatch(profile, amountValue)) {
      score += 20;
      reasons.push("Esta dentro de tu rango de monto preferido.");
    }

    const overlap = keywordOverlap(profile, `${record.title} ${record.description ?? ""}`);

    if (overlap.length) {
      score += Math.min(20, overlap.length * 5);
      reasons.push(`Coincidencia de palabras clave: ${overlap.slice(0, 3).join(", ")}.`);
    }

    const amountLabel = amountValue == null ? null : `${record.currency ?? "MXN"} ${amountValue.toLocaleString("es-MX", { maximumFractionDigits: 2 })}`;

    return {
      id: record.id,
      score,
      reasons,
      title: record.title,
      description: record.description,
      amount: amountLabel,
      procedureType: record.procedureType,
      category: record.category,
      publishDate: record.publishDate?.toISOString() ?? null,
      municipalityName: record.municipality.municipalityName,
      stateName: record.municipality.stateName,
      source: record.source,
    } satisfies RecommendationResultItem;
  });

  const results = scored.sort((a, b) => b.score - a.score).slice(0, 50);

  return {
    profileId: profile.id,
    results,
  };
}
