"""
services/country_utils.py
=========================
Country normalization utilities — public API layer over the in-memory
country snapshot.

All ORM access has been removed from this module.  Lookups are now pure
dictionary operations against the snapshot loaded at Django startup by
ServicesConfig.ready().

Public API (unchanged — backward compatible)
--------------------------------------------
  resolve_istat_country_code(value)  → ISO3 canonical code or None
  normalize_country(value)           → ISO2 code or raw fallback (ISTAT pipeline)
  is_iso2_country(value)             → bool
  country_name_from_iso2(value)      → display name or None
  normalize_country_fields(payload)  → dict with ISO2-normalized country fields
  clear_country_cache()              → no-op kept for call-site compatibility

IstatCountry schema (production)
---------------------------------
  code     — primary key VARCHAR(9), ISO3 alpha (e.g. "USA", "ITA")
  iso_code — VARCHAR(3), ISO2 (e.g. "US", "IT") or ISO3 when no ISO2
  name     — display name (e.g. "United States", "Italy")

Normalization contract for Alloggiati
--------------------------------------
  resolve_istat_country_code() returns IstatCountry.code (ISO3).
  normalize_country() returns ISO2 for backward compat with ISTAT pipeline.
"""

from __future__ import annotations

COUNTRY_FIELD_NAMES = (
    "country_of_birth",
    "document_issuing_country",
    "nationality",
    "country",
)


# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------

def _normalize_key(value: str | None) -> str:
    return str(value or "").strip().lower()


def _clean_value(value: str | None) -> str:
    return str(value or "").strip()


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def clear_country_cache() -> None:
    """
    No-op kept for call-site compatibility.

    The snapshot is now managed by services.country_snapshot.
    Use reset_country_snapshot() + load_country_snapshot() in tests instead.
    """


def is_iso2_country(value: str | None) -> bool:
    """Return True if value looks like a 2-letter ISO alpha code."""
    raw = _clean_value(value).upper()
    return len(raw) == 2 and raw.isalpha()


def resolve_istat_country_code(value: str | None) -> str | None:
    """
    Resolve any country representation to IstatCountry.code (canonical ISO3).

    Delegates to the in-memory snapshot — zero ORM, O(1).

    Resolution order:
      1. Empty / None                → None
      2. Exact match on code (ISO3)  → return code  ("USA" → "USA")
      3. Match on iso_code (ISO2)    → return code  ("US"  → "USA")
      4. Match on country name       → return code  ("Italy" → "ITA")
      5. No match                    → None

    Returns None (not "") to preserve the existing contract for callers
    that check `if resolve_istat_country_code(x):`.

    Args:
        value: Any country representation.

    Returns:
        IstatCountry.code string if resolved, otherwise None.
        Never raises.
    """
    from services.country_snapshot import resolve_istat_country_code as _snap_resolve

    result = _snap_resolve(value)
    return result if result else None


def normalize_country(value: str | None) -> str | None:
    """
    Normalize a country value to ISO2 for backward compatibility.

    Used by the ISTAT export pipeline and other existing callers.
    NOT used by the Alloggiati transformer (which calls resolve_istat_country_code).

    Returns ISO2 code if resolvable, otherwise the original stripped value
    (preserving existing behavior for callers that rely on the passthrough).

    Raises SnapshotNotLoadedError if the snapshot was not loaded at startup.
    This is intentional fail-fast behavior — no lazy loading.
    """
    from services.country_snapshot import (
        ISO2_TO_CODE,
        ISO3_TO_CODE,
        CODE_TO_ISO2,
        NAME_TO_ISO2,
        _snapshot_loaded,
        SnapshotNotLoadedError,
    )

    raw_value = _clean_value(value)
    if not raw_value:
        return None

    # Fail-fast: snapshot must be loaded before any lookup is attempted.
    if not _snapshot_loaded:
        raise SnapshotNotLoadedError(
            "Country snapshot has not been loaded. "
            "ServicesConfig.ready() must run before normalize_country() is called."
        )

    upper_value = raw_value.upper()

    # Already ISO2 — return immediately.
    if is_iso2_country(upper_value):
        return upper_value

    # ISO3 / canonical → look up ISO2
    if upper_value in ISO3_TO_CODE:
        canonical = ISO3_TO_CODE[upper_value]
        iso2 = CODE_TO_ISO2.get(canonical.upper())
        if iso2:
            return iso2

    # Name lookup → ISO2
    name_key = _normalize_key(raw_value)
    if name_key in NAME_TO_ISO2:
        return NAME_TO_ISO2[name_key]

    # Fallback: return original value (preserves existing behavior for ISTAT pipeline).
    return raw_value


def country_name_from_iso2(value: str | None) -> str | None:
    """Return the display name for an ISO2 code, or None if not found.

    Raises SnapshotNotLoadedError if the snapshot was not loaded at startup.
    """
    from services.country_snapshot import (
        ISO2_TO_CODE,
        NAME_TO_CODE,
        _snapshot_loaded,
        SnapshotNotLoadedError,
    )

    normalized = normalize_country(value)
    if not normalized or not is_iso2_country(normalized):
        return None

    # Fail-fast: snapshot must be loaded before any lookup is attempted.
    if not _snapshot_loaded:
        raise SnapshotNotLoadedError(
            "Country snapshot has not been loaded. "
            "ServicesConfig.ready() must run before country_name_from_iso2() is called."
        )

    # ISO2 → ISO3 → name is not directly stored; use the snapshot's name maps.
    # We look up the canonical code for this ISO2, then find the name.
    iso2_upper = normalized.upper()
    canonical = ISO2_TO_CODE.get(iso2_upper)
    if not canonical:
        return normalized

    # Reverse-scan NAME_TO_CODE for the display name.
    # This is only called for display purposes, not in the hot path.
    for name, code in NAME_TO_CODE.items():
        if code == canonical:
            return name.title()

    return normalized


def normalize_country_fields(payload: dict | None) -> dict:
    """
    Normalize all country fields in a payload dict to ISO2.
    Used by the ISTAT export pipeline.
    """
    if not isinstance(payload, dict):
        return {}

    normalized = payload.copy()
    for field_name in COUNTRY_FIELD_NAMES:
        if field_name in normalized:
            normalized[field_name] = normalize_country(normalized.get(field_name))
    return normalized
