"""Résolution des pays d'un fichier d'import : libellé brut → ISO 3166-1 alpha-2.

Spec « Multi-sources et cloisonnement pays » §4.1. Trois adaptateurs en dépendent (Mangools
lit `Location`, `generic_csv` une colonne au choix, un futur Semrush Positions son `Database`)
et le pipeline en dépend une fois par ligne : la résolution vit donc ici, jamais dupliquée
dans un adaptateur, et se teste seule — ce module n'importe ni la base, ni les modèles, ni
FastAPI.

**Les données ISO viennent de `pycountry`**, pas d'une liste de 200 pays recopiée dans le
dépôt qui se périmerait sans que personne ne s'en aperçoive. Par-dessus, deux tables courtes
et explicites, chacune motivée par un comportement mesuré de `pycountry` :

- `ALIASES` rattrape ce que la bibliothèque ne sait pas lire (`uk`, `Turkey`, `Russia`) ;
- `AMBIGUOUS` **refuse** ce qu'elle lit trop bien : sur une source qui cible des localités,
  `lookup("Georgia")` rend `GE` et `lookup("Jersey")` rend `JE`. L'État américain deviendrait
  la Géorgie et les lignes partiraient dans la mauvaise cible, sans un mot.

Un libellé non résolu n'est **jamais deviné** : il rend `None`, la ligne est ignorée et son
libellé brut remonte dans le rapport d'import (§4.5, « pays non reconnu : New York 3 »). Un
trou de table est donc bruyant, ce qui est exactement ce qu'on veut d'une table écrite à la
main : le rapport dit quoi y ajouter.

Contrat de casse, imposé par la base (`api/app/sites/models.py:82-83`) : le code pays sort en
MAJUSCULES (`ck_site_targets_country`, `^[A-Z]{2}$`) et la langue en minuscules
(`ck_site_targets_language`, `^[a-z]{2}$`). Rendre autre chose ferait remonter une
IntegrityError 23514 à la création de la cible, au lieu d'une erreur lisible ici.
"""

import re
from collections.abc import Mapping
from functools import lru_cache

import pycountry
from babel import Locale, UnknownLocaleError

# Libellés qu'un export écrit et que `pycountry` ne sait pas résoudre (vérifié sur 26.2.16 :
# les trois lèvent `LookupError`). Clés DÉJÀ normalisées — `strip()` puis `casefold()` — parce
# que `resolve` cherche la forme normalisée : « UK » et « uk » tombent tous deux sur `"uk"`.
#
# La table reste courte par principe : on n'y ajoute qu'un libellé **constaté** dans un export
# réel. Autres échecs mesurés de `pycountry`, volontairement absents tant qu'aucune fixture ne
# les montre : « Ivory Coast », « Cape Verde », « Swaziland », « Macau », « Brunei », « UAE ».
# Ils remonteront d'eux-mêmes dans le rapport d'import le jour où un fichier les portera.
#
# « Korea » n'y figure pas non plus, mais pour une autre raison : il désigne deux pays (KP et
# KR). Le résoudre serait deviner. « South Korea » et « North Korea » passent déjà.
ALIASES: dict[str, str] = {
    "uk": "GB",
    "turkey": "TR",  # l'ISO ne connaît plus que « Türkiye » depuis 2022
    "russia": "RU",  # l'ISO ne connaît que « Russian Federation »
}

# Libellés que `pycountry` résout, mais qu'on refuse : ce sont des localités homonymes d'un
# pays, et les sources visées (KWFinder/Mangools) ciblent des localités. Vérifié :
# `lookup("Georgia")` → `GE`, `lookup("Jersey")` → `JE`. Ils rendent `None` et sont comptés
# « pays non reconnu », ce qui les rend visibles au lieu de les envoyer ailleurs en silence.
AMBIGUOUS: frozenset[str] = frozenset({"georgia", "jersey"})

# `Locale.parse("und_" + code)` accepte beaucoup trop : « None » (4 lettres) est lu comme un
# code de script et rend « en ». On ne lui donne donc que ce qui a la forme d'un alpha-2.
_ALPHA_2 = re.compile(r"[A-Za-z]{2}")

# `ck_site_targets_language` n'accepte que deux minuscules. CLDR rend parfois autre chose
# (`fil` pour PH, `zh_Hant` pour certaines requêtes) : hors gabarit, on ne propose rien.
_LANGUAGE = re.compile(r"[a-z]{2}")


def resolve(raw: str | None) -> str | None:
    """Le code ISO 3166-1 alpha-2 MAJUSCULE d'un libellé de pays, ou `None`.

    `None` couvre trois cas volontairement indistincts pour l'appelant : la valeur est vide,
    c'est une localité (« New York »), ou c'est un libellé que ni les alias ni `pycountry` ne
    connaissent. Les trois se traitent pareil en aval — la ligne est ignorée et le libellé
    brut est rapporté tel quel (§4.5).
    """
    value = (raw or "").strip()
    if not value:
        return None
    # `pycountry.countries.lookup` ne détoure PAS : `lookup("  united states  ")` lève
    # LookupError. Le strip ci-dessus est ce qui rend « casse et espaces indifférents » vrai.
    normalized = value.casefold()
    if normalized in AMBIGUOUS:
        return None
    alias = ALIASES.get(normalized)
    if alias is not None:
        return alias
    try:
        return pycountry.countries.lookup(value).alpha_2
    except LookupError:
        return None


def suggested_language(code: str) -> str | None:
    """La langue à proposer par défaut pour ce pays (§4.4), ou `None` si rien de sûr.

    **Proposition, jamais imposition** : l'utilisateur qui veut cibler l'anglais en Allemagne
    le dit (§4.4, décision B3).

    `pycountry` n'a AUCUNE relation pays → langue (ses champs sont exactement `alpha_2`,
    `alpha_3`, `flag`, `name`, `numeric`, `official_name`) : la langue vient de Babel, par les
    *likely subtags* CLDR. `babel.languages.get_official_languages` est inutilisable ici —
    mesuré : `()` pour US et AU, `("mi",)` pour NZ (le māori), `zh_Hant` pour HK.
    """
    if not _ALPHA_2.fullmatch(code.strip() if code else ""):
        return None
    try:
        language = Locale.parse("und_" + code.strip().upper()).language
    except (ValueError, UnknownLocaleError):
        return None
    if language and _LANGUAGE.fullmatch(language):
        return language
    return None


def label(code: str) -> str:
    """Le nom français du pays, avec repli sur le code brut.

    Sert au web : `web/lib/locales.ts` ne connaît que 30 pays et remplace silencieusement tout
    code inconnu par `FR`. C'est l'api qui nomme, une fois, avec les données CLDR.
    """
    value = (code or "").strip()
    return _french_territories().get(value.upper(), value)


@lru_cache(maxsize=1)
def _french_territories() -> Mapping[str, str]:
    # Chargé à la première demande, pas à l'import : le pipeline résout des pays sans jamais
    # avoir besoin de les nommer.
    return Locale("fr").territories
