import { createHash } from "node:crypto";
import * as XLSX from "xlsx";
import type { AssociationDirectoryEntry, InsertAssociation } from "../drizzle/schema";
import { TRPCError } from "@trpc/server";
import { normalizeLegalRepresentativeRole, serializeAssociationGovernance } from "@shared/associationGovernance";

type ParsedDirectoryRow = {
  sheetName: string;
  rowNumber: number;
  nomAssociation: string;
  emailOfficiel: string | null;
  emailOfficielNormalise: string | null;
  siret: string | null;
  rna: string | null;
  adresse: string | null;
  codePostal: string | null;
  ville: string | null;
  telephone: string | null;
  siteWeb: string | null;
  facebookUrl: string | null;
  instagramUrl: string | null;
  dateCreation: Date | null;
  objetAssociation: string | null;
  statutJuridique: "association_loi_1901" | "association_reconnue_utilite_publique" | "fondation" | "autre";
  nomRepresentant: string | null;
  fonctionRepresentant: string | null;
  sourceFingerprint: string;
};

type PreviewRow = {
  sheetName: string;
  rowNumber: number;
  nomAssociation: string;
  emailOfficiel: string | null;
  ville: string | null;
  status: "valid" | "missing_email" | "duplicate_email";
  message: string;
};

export type DuplicateEmailGroup = {
  emailOfficielNormalise: string;
  emailOfficiel: string;
  rowNumbers: number[];
  options: Array<{
    sheetName: string;
    rowNumber: number;
    nomAssociation: string;
    ville: string | null;
  }>;
};

export type DirectoryPreviewResult = {
  fileName: string;
  totalRows: number;
  validRows: number;
  missingEmailRows: number;
  duplicateEmailRows: number;
  previewRows: PreviewRow[];
  duplicateGroups: DuplicateEmailGroup[];
  allRows: ParsedDirectoryRow[];
  importableRows: ParsedDirectoryRow[];
};

const headerAliases: Record<string, string[]> = {
  nomAssociation: ["nom association", "association", "nom", "raison sociale", "nom de la structure"],
  emailOfficiel: ["email officiel", "email", "mail", "courriel", "adresse email"],
  siret: ["siret", "numéro siret", "numero siret"],
  rna: ["rna", "numéro rna", "numero rna"],
  adresse: ["adresse", "adresse siège", "adresse siege", "adresse du siège"],
  codePostal: ["code postal", "cp"],
  ville: ["ville", "commune"],
  telephone: ["telephone", "téléphone", "tel", "tél", "tel.", "port.", "port"],
  siteWeb: ["site web", "site", "website", "url site"],
  facebookUrl: ["facebook", "facebook url", "facebook link", "lien facebook", "url facebook"],
  instagramUrl: ["instagram", "instagram url", "instagram link", "lien instagram", "url instagram"],
  dateCreation: ["date création", "date creation", "creation", "date de création"],
  objetAssociation: ["objet", "objet association", "activité", "activités", "activite"],
  statutJuridique: ["statut", "statut juridique"],
  nomRepresentant: ["nom représentant", "nom representant", "président", "president", "responsable", "president"],
  fonctionRepresentant: ["fonction représentant", "fonction representant", "fonction", "qualité", "qualite", "secretaire", "secrétaire"],
};

function normalizeHeader(value: string) {
  return value
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .trim()
    .toLowerCase();
}

function cleanString(value: unknown) {
  if (value === null || value === undefined) return null;
  const text = String(value).trim();
  return text ? text : null;
}

function extractFirstEmail(value: unknown) {
  const text = cleanString(value);
  if (!text) return null;
  const match = text.match(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i);
  return match?.[0] ?? null;
}

function normalizeEmail(value: string | null) {
  return value ? value.trim().toLowerCase() : null;
}

function normalizeSiret(value: string | null) {
  return value ? value.replace(/\D/g, "") || null : null;
}

function normalizeRna(value: string | null) {
  return value ? value.replace(/\s+/g, "").toUpperCase() : null;
}

function normalizeSiteWeb(value: string | null) {
  if (!value) return null;
  if (/^https?:\/\//i.test(value)) return value;
  return `https://${value}`;
}

function normalizeTelephone(value: string | null) {
  if (!value) return null;
  const first = value
    .split(/[\/;,]/)
    .map((part) => part.trim())
    .find(Boolean);

  return first ? first.slice(0, 20) : null;
}

function normalizeDate(value: unknown) {
  if (value === null || value === undefined || value === "") return null;

  if (value instanceof Date && !Number.isNaN(value.getTime())) {
    return value;
  }

  if (typeof value === "number") {
    const parsed = XLSX.SSF.parse_date_code(value);
    if (parsed) {
      return new Date(Date.UTC(parsed.y, parsed.m - 1, parsed.d));
    }
  }

  const parsed = new Date(String(value));
  if (Number.isNaN(parsed.getTime())) return null;
  return parsed;
}

function normalizeStatut(value: string | null): ParsedDirectoryRow["statutJuridique"] {
  if (!value) return "association_loi_1901";
  const normalized = normalizeHeader(value);
  if (normalized.includes("utilite publique")) return "association_reconnue_utilite_publique";
  if (normalized.includes("fondation")) return "fondation";
  if (normalized.includes("1901") || normalized.includes("association")) return "association_loi_1901";
  return "autre";
}

function computeFingerprint(row: Omit<ParsedDirectoryRow, "sourceFingerprint">) {
  return createHash("sha256").update(JSON.stringify(row)).digest("hex");
}

function getCommuneFromSheetName(sheetName: string) {
  const normalized = normalizeHeader(sheetName).replace(/[-_]/g, " ");
  if (normalized.includes("kourou")) return "Kourou";
  if (normalized.includes("sinnamary")) return "Sinnamary";
  if (normalized.includes("iracoubo")) return "Iracoubo";
  if (normalized.includes("st elie") || normalized.includes("saint elie")) return "Saint-Élie";
  return null;
}

function resolveColumnIndex(headers: string[], field: keyof typeof headerAliases) {
  const aliases = headerAliases[field];
  return headers.findIndex(header => aliases.includes(normalizeHeader(header)));
}

function readCell(row: unknown[], headers: string[], field: keyof typeof headerAliases) {
  const index = resolveColumnIndex(headers, field);
  if (index === -1) return null;
  return row[index];
}

function resolveEmailValue(row: unknown[], headers: string[]) {
  const direct = extractFirstEmail(readCell(row, headers, "emailOfficiel"));
  if (direct) return direct;

  for (const cell of row) {
    const extracted = extractFirstEmail(cell);
    if (extracted) return extracted;
  }

  return null;
}

export function parseAssociationDirectoryWorkbook(fileBuffer: Buffer, fileName: string): DirectoryPreviewResult {
  const workbook = XLSX.read(fileBuffer, { type: "buffer", cellDates: true });
  if (workbook.SheetNames.length === 0) {
    throw new TRPCError({ code: "BAD_REQUEST", message: "Le fichier Excel ne contient aucune feuille exploitable" });
  }

  const importableRows: ParsedDirectoryRow[] = [];
  const previewRows: PreviewRow[] = [];
  const emailRowMap = new Map<string, number[]>();
  let hasAnyUsableSheet = false;

  workbook.SheetNames.forEach((sheetName) => {
    const sheet = workbook.Sheets[sheetName];
    if (!sheet) return;

    const rows = XLSX.utils.sheet_to_json<unknown[]>(sheet, { header: 1, defval: null });
    if (rows.length < 2) return;

    const headers = (rows[0] || []).map(value => String(value ?? ""));
    if (resolveColumnIndex(headers, "nomAssociation") === -1) return;

    hasAnyUsableSheet = true;
    const communeFromSheet = getCommuneFromSheetName(sheetName);

    rows.slice(1).forEach((rawRow, index) => {
      const rowNumber = index + 2;
      const nomAssociation = cleanString(readCell(rawRow, headers, "nomAssociation"));

      if (!nomAssociation) {
        return;
      }

      const emailOfficiel = resolveEmailValue(rawRow, headers);
      const emailOfficielNormalise = normalizeEmail(emailOfficiel);
      const rawAdresse = cleanString(readCell(rawRow, headers, "adresse"));
      const parsedRowBase = {
        sheetName,
        rowNumber,
        nomAssociation,
        emailOfficiel,
        emailOfficielNormalise,
        siret: normalizeSiret(cleanString(readCell(rawRow, headers, "siret"))),
        rna: normalizeRna(cleanString(readCell(rawRow, headers, "rna"))),
        adresse: rawAdresse && extractFirstEmail(rawAdresse) ? null : rawAdresse,
        codePostal: cleanString(readCell(rawRow, headers, "codePostal")),
        ville: communeFromSheet || cleanString(readCell(rawRow, headers, "ville")),
        telephone: normalizeTelephone(cleanString(readCell(rawRow, headers, "telephone"))),
        siteWeb: normalizeSiteWeb(cleanString(readCell(rawRow, headers, "siteWeb"))),
        facebookUrl: normalizeSiteWeb(cleanString(readCell(rawRow, headers, "facebookUrl"))),
        instagramUrl: normalizeSiteWeb(cleanString(readCell(rawRow, headers, "instagramUrl"))),
        dateCreation: normalizeDate(readCell(rawRow, headers, "dateCreation")),
        objetAssociation: cleanString(readCell(rawRow, headers, "objetAssociation")),
        statutJuridique: normalizeStatut(cleanString(readCell(rawRow, headers, "statutJuridique"))),
        nomRepresentant: cleanString(readCell(rawRow, headers, "nomRepresentant")),
        fonctionRepresentant: cleanString(readCell(rawRow, headers, "fonctionRepresentant")),
      };

      const parsedRow: ParsedDirectoryRow = {
        ...parsedRowBase,
        sourceFingerprint: computeFingerprint(parsedRowBase),
      };

      importableRows.push(parsedRow);

      if (emailOfficielNormalise) {
        const refs = emailRowMap.get(emailOfficielNormalise) || [];
        refs.push(rowNumber);
        emailRowMap.set(emailOfficielNormalise, refs);
      }
    });
  });

  if (!hasAnyUsableSheet || importableRows.length === 0) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "Le fichier Excel ne contient pas de feuille exploitable avec une colonne de nom d'association",
    });
  }

  importableRows.forEach(row => {
    const duplicateRows = row.emailOfficielNormalise ? emailRowMap.get(row.emailOfficielNormalise) || [] : [];

    if (!row.emailOfficielNormalise) {
      previewRows.push({
        sheetName: row.sheetName,
        rowNumber: row.rowNumber,
        nomAssociation: row.nomAssociation,
        emailOfficiel: row.emailOfficiel,
        ville: row.ville,
        status: "missing_email",
        message: `Feuille ${row.sheetName} : email officiel manquant, la ligne ne pourra pas être rattachée automatiquement`,
      });
      return;
    }

    if (duplicateRows.length > 1) {
      previewRows.push({
        sheetName: row.sheetName,
        rowNumber: row.rowNumber,
        nomAssociation: row.nomAssociation,
        emailOfficiel: row.emailOfficiel,
        ville: row.ville,
        status: "duplicate_email",
        message: `Feuille ${row.sheetName} : email dupliqué dans le fichier (lignes ${duplicateRows.join(", ")})`,
      });
      return;
    }

    previewRows.push({
      sheetName: row.sheetName,
      rowNumber: row.rowNumber,
      nomAssociation: row.nomAssociation,
      emailOfficiel: row.emailOfficiel,
      ville: row.ville,
      status: "valid",
      message: `Feuille ${row.sheetName} : ligne prête à être importée`,
    });
  });

  const importableRowKeys = new Set(
    previewRows
      .filter(row => row.status !== "duplicate_email")
      .map(row => `${row.rowNumber}::${row.nomAssociation}`)
  );

  const duplicateGroups: DuplicateEmailGroup[] = Array.from(emailRowMap.entries())
    .filter(([, rowNumbers]) => rowNumbers.length > 1)
    .map(([emailOfficielNormalise, rowNumbers]) => {
      const options = importableRows
        .filter(row => row.emailOfficielNormalise === emailOfficielNormalise)
        .map(row => ({
          sheetName: row.sheetName,
          rowNumber: row.rowNumber,
          nomAssociation: row.nomAssociation,
          ville: row.ville,
        }));

      return {
        emailOfficielNormalise,
        emailOfficiel: options.length > 0 ? importableRows.find(row => row.emailOfficielNormalise === emailOfficielNormalise)?.emailOfficiel || emailOfficielNormalise : emailOfficielNormalise,
        rowNumbers,
        options,
      };
    });

  return {
    fileName,
    totalRows: importableRows.length,
    validRows: previewRows.filter(row => row.status === "valid").length,
    missingEmailRows: previewRows.filter(row => row.status === "missing_email").length,
    duplicateEmailRows: previewRows.filter(row => row.status === "duplicate_email").length,
    previewRows,
    duplicateGroups,
    allRows: importableRows,
    importableRows: importableRows.filter(row => importableRowKeys.has(`${row.rowNumber}::${row.nomAssociation}`)),
  };
}

export function resolveAssociationDirectoryImportRows(
  preview: DirectoryPreviewResult,
  duplicateSelections?: Record<string, number>,
) {
  const selectedDuplicateKeys = new Set<string>();

  Object.entries(duplicateSelections || {}).forEach(([email, rowNumber]) => {
    const numericRow = Number(rowNumber);
    if (Number.isFinite(numericRow) && numericRow > 0) {
      selectedDuplicateKeys.add(`${email}::${numericRow}`);
    }
  });

  return preview.allRows.filter((row) => {
    if (!row.emailOfficielNormalise) {
      return true;
    }

    const isDuplicate = preview.duplicateGroups.some(group => group.emailOfficielNormalise === row.emailOfficielNormalise);
    if (!isDuplicate) {
      return true;
    }

    return selectedDuplicateKeys.has(`${row.emailOfficielNormalise}::${row.rowNumber}`);
  });
}

export function createAssociationProfileFromDirectoryEntry(userId: number, entry: AssociationDirectoryEntry): InsertAssociation {
  const governance = entry.nomRepresentant || entry.fonctionRepresentant
    ? serializeAssociationGovernance({
        representantLegal: {
          nom: entry.nomRepresentant || "",
          prenom: "",
          email: "",
          telephone: "",
          fonction: normalizeLegalRepresentativeRole(entry.fonctionRepresentant),
        },
        membres: [],
      })
    : null;

  return {
    userId,
    sourceDirectoryEntryId: entry.id,
    nomAssociation: entry.nomAssociation,
    siret: entry.siret ?? null,
    rna: entry.rna ?? null,
    thematique: entry.thematique ?? null,
    adresse: entry.adresse ?? null,
    codePostal: entry.codePostal ?? null,
    ville: entry.ville ?? null,
    telephone: entry.telephone ?? null,
    emailContact: entry.emailOfficiel ?? null,
    siteWeb: entry.siteWeb ?? null,
    facebookUrl: entry.facebookUrl ?? null,
    instagramUrl: entry.instagramUrl ?? null,
    dateCreation: entry.dateCreation ?? null,
    objetAssociation: entry.objetAssociation ?? null,
    statutJuridique: entry.statutJuridique ?? "association_loi_1901",
    nomRepresentant: entry.nomRepresentant ?? null,
    fonctionRepresentant: entry.fonctionRepresentant ?? null,
    gouvernance: governance,
    profileComplete: Boolean(entry.nomAssociation && entry.adresse && entry.ville),
    isActive: true,
  };
}
