import type { Association, AssociationDirectoryEntry, InsertAssociationDirectoryEntry } from "../drizzle/schema";
import type { AssociationGeoPrecision, AssociationGeoSource } from "@shared/associationGeo";

type CoordinateSet = {
  latitude: string | null;
  longitude: string | null;
  geoSource: AssociationGeoSource | null;
  externalSourceStatus: string;
  externalSourceLabel: string | null;
};

function formatCoordinate(value: number | null) {
  if (value === null || Number.isNaN(value)) return null;
  return value.toFixed(6);
}

function buildAddressQuery(entry: AssociationDirectoryEntry, association?: Association | null) {
  const adresse = association?.adresse || entry.adresse;
  const codePostal = association?.codePostal || entry.codePostal;
  const ville = association?.ville || entry.ville;
  return [adresse, codePostal, ville].filter(Boolean).join(" ").trim();
}

async function geocodeAddress(query: string) {
  const url = new URL("https://api-adresse.data.gouv.fr/search/");
  url.searchParams.set("q", query);
  url.searchParams.set("limit", "1");

  const response = await fetch(url, {
    headers: {
      "Accept": "application/json",
      "User-Agent": "portail-associations/1.0",
    },
  });

  if (!response.ok) {
    throw new Error(`Adresse API returned ${response.status}`);
  }

  const payload = await response.json() as {
    features?: Array<{
      geometry?: { coordinates?: [number, number] };
      properties?: { label?: string };
    }>;
  };

  const first = payload.features?.[0];
  const coordinates = first?.geometry?.coordinates;
  if (!coordinates || coordinates.length < 2) {
    return null;
  }

  return {
    latitude: coordinates[1],
    longitude: coordinates[0],
    label: first?.properties?.label || null,
  };
}

async function fetchCommuneCenter(ville: string, codePostal?: string | null) {
  const url = new URL("https://geo.api.gouv.fr/communes");
  url.searchParams.set("nom", ville);
  url.searchParams.set("fields", "nom,centre,code,codesPostaux");
  url.searchParams.set("format", "json");
  url.searchParams.set("geometry", "centre");
  if (codePostal) {
    url.searchParams.set("codePostal", codePostal);
  }

  const response = await fetch(url, {
    headers: {
      "Accept": "application/json",
      "User-Agent": "portail-associations/1.0",
    },
  });

  if (!response.ok) {
    throw new Error(`Geo API returned ${response.status}`);
  }

  const payload = await response.json() as Array<{
    nom?: string;
    centre?: { coordinates?: [number, number] };
  }>;

  const first = payload[0];
  const coordinates = first?.centre?.coordinates;
  if (!coordinates || coordinates.length < 2) {
    return null;
  }

  return {
    latitude: coordinates[1],
    longitude: coordinates[0],
    label: first?.nom || ville,
  };
}

export async function computeAssociationDirectoryGeoUpdate(entry: AssociationDirectoryEntry, association?: Association | null) {
  return computeAssociationDirectoryGeoUpdateForPrecision(
    entry,
    association,
    (entry.geoPrecision as AssociationGeoPrecision | null) || "commune_center"
  );
}

export async function computeAssociationDirectoryGeoUpdateForPrecision(
  entry: AssociationDirectoryEntry,
  association: Association | null | undefined,
  preferredPrecision: AssociationGeoPrecision
) {
  const updates: Partial<InsertAssociationDirectoryEntry> = {
    geoLastSyncedAt: new Date(),
    geoPrecision: preferredPrecision,
  };

  const addressQuery = buildAddressQuery(entry, association);
  if (preferredPrecision === "exact_address" && addressQuery) {
    try {
      const geocoded = await geocodeAddress(addressQuery);
      if (geocoded) {
        updates.latitude = formatCoordinate(geocoded.latitude);
        updates.longitude = formatCoordinate(geocoded.longitude);
        updates.geoSource = "adresse_gouv";
        updates.externalSourceStatus = "geocoded_from_address";
        updates.externalSourceLabel = geocoded.label || "Adresse.data.gouv.fr";
        return updates;
      }
    } catch {
      // Fall back to commune center below.
    }
  }

  const ville = association?.ville || entry.ville;
  const codePostal = association?.codePostal || entry.codePostal;
  if (ville) {
    try {
      const communeCenter = await fetchCommuneCenter(ville, codePostal);
      if (communeCenter) {
        updates.latitude = formatCoordinate(communeCenter.latitude);
        updates.longitude = formatCoordinate(communeCenter.longitude);
        updates.geoSource = "commune_center";
        updates.externalSourceStatus = "commune_center_fallback";
        updates.externalSourceLabel = communeCenter.label || ville;
        return updates;
      }
    } catch {
      // Fall through to unavailable state.
    }
  }

  updates.latitude = null;
  updates.longitude = null;
  updates.geoSource = null;
  updates.externalSourceStatus = "unresolved";
  updates.externalSourceLabel = null;
  return updates;
}

export function getPublicMapCoordinates(entry: AssociationDirectoryEntry) {
  if (!entry.latitude || !entry.longitude) {
    return null;
  }

  const latitude = Number(entry.latitude);
  const longitude = Number(entry.longitude);
  if (Number.isNaN(latitude) || Number.isNaN(longitude)) {
    return null;
  }

  return {
    latitude,
    longitude,
    precision: (
      entry.geoPrecision === "hidden"
        ? (entry.geoSource === "commune_center" ? "commune_center" : "exact_address")
        : entry.geoPrecision
    ) as AssociationGeoPrecision,
  };
}
