import type { AssociationDirectoryEntry } from "../drizzle/schema";

export type AssociationDirectoryMatchInput = {
  nomAssociation?: string | null;
  email?: string | null;
  siret?: string | null;
  rna?: string | null;
  ville?: string | null;
};

export type AssociationDirectoryMatchCandidate = Pick<
  AssociationDirectoryEntry,
  "id" | "nomAssociation" | "emailOfficiel" | "siret" | "rna" | "ville"
>;

export type AssociationDirectoryMatchResult = {
  status: "matched" | "ambiguous" | "none";
  matchedEntry?: AssociationDirectoryMatchCandidate;
  candidates: AssociationDirectoryMatchCandidate[];
  reason: string;
};

function normalizeText(value?: string | null) {
  return String(value || "")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-zA-Z0-9]+/g, " ")
    .trim()
    .toLowerCase();
}

export function normalizeDirectoryEmail(value?: string | null) {
  const trimmed = String(value || "").trim().toLowerCase();
  return trimmed || null;
}

export function normalizeDirectorySiret(value?: string | null) {
  const digits = String(value || "").replace(/\D/g, "");
  return digits || null;
}

export function normalizeDirectoryRna(value?: string | null) {
  const normalized = String(value || "").replace(/\s+/g, "").trim().toUpperCase();
  return normalized || null;
}

function candidateScore(candidate: AssociationDirectoryMatchCandidate, input: AssociationDirectoryMatchInput) {
  const localName = normalizeText(input.nomAssociation);
  const localCity = normalizeText(input.ville);
  const localEmail = normalizeDirectoryEmail(input.email);
  const localSiret = normalizeDirectorySiret(input.siret);
  const localRna = normalizeDirectoryRna(input.rna);

  const candidateName = normalizeText(candidate.nomAssociation);
  const candidateCity = normalizeText(candidate.ville);
  const candidateEmail = normalizeDirectoryEmail(candidate.emailOfficiel);
  const candidateSiret = normalizeDirectorySiret(candidate.siret);
  const candidateRna = normalizeDirectoryRna(candidate.rna);

  let score = 0;
  const exactSiret = Boolean(localSiret && candidateSiret && localSiret === candidateSiret);
  const exactRna = Boolean(localRna && candidateRna && localRna === candidateRna);
  const exactEmail = Boolean(localEmail && candidateEmail && localEmail === candidateEmail);
  const exactName = Boolean(localName && candidateName && localName === candidateName);
  const exactCity = Boolean(localCity && candidateCity && localCity === candidateCity);

  if (exactSiret) score += 300;
  if (exactRna) score += 260;
  if (exactEmail) score += 220;
  if (exactName) score += 120;
  if (exactCity) score += 25;

  if (!exactName && localName && candidateName) {
    if (candidateName.includes(localName) || localName.includes(candidateName)) {
      score += 40;
    }
  }

  return { score, exactSiret, exactRna, exactEmail, exactName, exactCity };
}

export function matchAssociationDirectoryEntry(
  entries: AssociationDirectoryMatchCandidate[],
  input: AssociationDirectoryMatchInput
): AssociationDirectoryMatchResult {
  const normalizedName = normalizeText(input.nomAssociation);
  const normalizedEmail = normalizeDirectoryEmail(input.email);
  const normalizedSiret = normalizeDirectorySiret(input.siret);
  const normalizedRna = normalizeDirectoryRna(input.rna);
  const normalizedCity = normalizeText(input.ville);

  if (!normalizedName && !normalizedEmail && !normalizedSiret && !normalizedRna) {
    return {
      status: "none",
      candidates: [],
      reason: "Aucun identifiant exploitable n'a été fourni pour rechercher une fiche du bordereau.",
    };
  }

  const exactSiret = entries.filter((entry) => normalizeDirectorySiret(entry.siret) === normalizedSiret && normalizedSiret);
  if (exactSiret.length === 1) {
    return { status: "matched", matchedEntry: exactSiret[0], candidates: exactSiret, reason: "Correspondance validée par le SIRET." };
  }
  if (exactSiret.length > 1) {
    return { status: "ambiguous", candidates: exactSiret, reason: "Plusieurs fiches du bordereau portent le même SIRET." };
  }

  const exactRna = entries.filter((entry) => normalizeDirectoryRna(entry.rna) === normalizedRna && normalizedRna);
  if (exactRna.length === 1) {
    return { status: "matched", matchedEntry: exactRna[0], candidates: exactRna, reason: "Correspondance validée par le RNA." };
  }
  if (exactRna.length > 1) {
    return { status: "ambiguous", candidates: exactRna, reason: "Plusieurs fiches du bordereau portent le même RNA." };
  }

  const exactEmail = entries.filter((entry) => normalizeDirectoryEmail(entry.emailOfficiel) === normalizedEmail && normalizedEmail);
  if (exactEmail.length === 1) {
    return { status: "matched", matchedEntry: exactEmail[0], candidates: exactEmail, reason: "Correspondance validée par l'email officiel." };
  }
  if (exactEmail.length > 1) {
    return { status: "ambiguous", candidates: exactEmail, reason: "Plusieurs fiches du bordereau utilisent le même email officiel." };
  }

  const scored = entries
    .map((entry) => ({ entry, ...candidateScore(entry, input) }))
    .filter((entry) => entry.score > 0)
    .sort((a, b) => b.score - a.score);

  const exactNameAndCity = scored.filter((entry) => entry.exactName && entry.exactCity);
  if (exactNameAndCity.length === 1) {
    return {
      status: "matched",
      matchedEntry: exactNameAndCity[0].entry,
      candidates: exactNameAndCity.map((entry) => entry.entry),
      reason: "Correspondance validée par le nom et la commune.",
    };
  }
  if (exactNameAndCity.length > 1) {
    return {
      status: "ambiguous",
      candidates: exactNameAndCity.map((entry) => entry.entry),
      reason: "Plusieurs fiches du bordereau correspondent au même nom dans cette commune.",
    };
  }

  const exactNameOnly = scored.filter((entry) => entry.exactName);
  if (exactNameOnly.length === 1) {
    return {
      status: "matched",
      matchedEntry: exactNameOnly[0].entry,
      candidates: exactNameOnly.map((entry) => entry.entry),
      reason: "Correspondance validée par le nom de l'association.",
    };
  }
  if (exactNameOnly.length > 1) {
    return {
      status: "ambiguous",
      candidates: exactNameOnly.map((entry) => entry.entry),
      reason: "Plusieurs fiches du bordereau portent le même nom.",
    };
  }

  const closeCandidates = scored
    .filter((entry) => entry.score >= 40)
    .map((entry) => entry.entry)
    .slice(0, 5);

  if (closeCandidates.length > 0) {
    return {
      status: "ambiguous",
      candidates: closeCandidates,
      reason: normalizedCity
        ? "Des rapprochements partiels ont été trouvés, mais aucun n'est assez fiable pour lier automatiquement cette association."
        : "Des rapprochements potentiels ont été trouvés, mais une validation humaine reste nécessaire.",
    };
  }

  return {
    status: "none",
    candidates: [],
    reason: "Aucune fiche du bordereau ne correspond de façon fiable à cette association.",
  };
}
