from __future__ import annotations

import pycountry
from django import forms
from django.core.exceptions import ValidationError

RUFORUM_COUNTRIES: tuple[tuple[str, str], ...] = (
    ("BJ", "Benin"),
    ("BW", "Botswana"),
    ("BF", "Burkina Faso"),
    ("BI", "Burundi"),
    ("CM", "Cameroon"),
    ("CF", "Central African Republic"),
    ("TD", "Chad"),
    ("CG", "Congo"),
    ("CD", "Democratic Republic of the Congo"),
    ("DJ", "Djibouti"),
    ("ET", "Ethiopia"),
    ("GA", "Gabon"),
    ("GM", "Gambia"),
    ("GH", "Ghana"),
    ("KE", "Kenya"),
    ("LS", "Lesotho"),
    ("LR", "Liberia"),
    ("MG", "Madagascar"),
    ("MW", "Malawi"),
    ("ML", "Mali"),
    ("MR", "Mauritania"),
    ("MU", "Mauritius"),
    ("MZ", "Mozambique"),
    ("NA", "Namibia"),
    ("NE", "Niger"),
    ("NG", "Nigeria"),
    ("RW", "Rwanda"),
    ("SN", "Senegal"),
    ("SL", "Sierra Leone"),
    ("SO", "Somalia"),
    ("ZA", "South Africa"),
    ("SS", "South Sudan"),
    ("SD", "Sudan"),
    ("SZ", "Eswatini"),
    ("TZ", "Tanzania"),
    ("TG", "Togo"),
    ("UG", "Uganda"),
    ("ZM", "Zambia"),
    ("ZW", "Zimbabwe"),
)

RUFORUM_COUNTRY_MAP: dict[str, str] = dict(RUFORUM_COUNTRIES)


def _all_country_pairs() -> tuple[tuple[str, str], ...]:
    pairs = [(c.alpha_2, c.name) for c in pycountry.countries if c.alpha_2]
    return tuple(sorted(pairs, key=lambda item: item[1].casefold()))


ALL_COUNTRY_PAIRS: tuple[tuple[str, str], ...] = _all_country_pairs()
# code -> full name (e.g. "UG" -> "Uganda")
ALL_COUNTRY_MAP: dict[str, str] = dict(ALL_COUNTRY_PAIRS)
# Country values are STORED as their full name (e.g. "Uganda"), so the choice
# value and label are both the name. ``_NAME_LOOKUP`` maps a casefolded name
# back to its canonical spelling; ``ALL_COUNTRY_NAME_SET`` is the validation set.
ALL_COUNTRY_CHOICES: tuple[tuple[str, str], ...] = tuple(
    (name, name) for _code, name in ALL_COUNTRY_PAIRS
)
ALL_COUNTRY_NAME_SET: frozenset[str] = frozenset(name for _code, name in ALL_COUNTRY_PAIRS)
_NAME_LOOKUP: dict[str, str] = {name.casefold(): name for _code, name in ALL_COUNTRY_PAIRS}

DEFAULT_COUNTRY_SELECT_ATTRS = {
    "class": (
        "w-full rounded border border-gray-300 px-3 py-2 text-sm "
        "focus:border-ruforum-primary focus:outline-none focus:ring-1 "
        "focus:ring-ruforum-primary"
    ),
    # House Select2 marker: every country <select> auto-inits as a searchable
    # dropdown wherever the page includes ``components/select2_auto_init.html``.
    # (A styled native control remains the fallback if JS fails to load.)
    "data-s2": True,
    "data-s2-placeholder": "Search countries…",
}


def normalize_country_code(value: object) -> str:
    if value is None:
        return ""
    return str(value).strip().upper()


def is_ruforum_country_code(value: object) -> bool:
    code = normalize_country_code(value)
    return not code or code in RUFORUM_COUNTRY_MAP


def to_country_name(value: object) -> str:
    """Resolve any country input to its canonical full name.

    Accepts a stored full name ("Uganda"), an ISO alpha-2 code ("UG"/"ug"), or
    a differently-cased name ("uganda") and returns the canonical full name
    ("Uganda"). The function is idempotent and tolerant: empty/None becomes ""
    and an unrecognised value is returned trimmed (never dropped), so legacy or
    free-text data is preserved rather than silently lost.
    """
    if value is None:
        return ""
    raw = str(value).strip()
    if not raw:
        return ""
    # ISO alpha-2 code (legacy stored values, form posts from older clients).
    code_hit = ALL_COUNTRY_MAP.get(raw.upper())
    if code_hit:
        return code_hit
    # Already a name (any casing).
    return _NAME_LOOKUP.get(raw.casefold(), raw)


def get_country_name(value: object, default: str = "") -> str:
    """Display helper: canonical full name for a stored value, else *default*.

    Country values are stored as full names now, but this stays idempotent so
    it also resolves any legacy ISO codes still in the data.
    """
    return to_country_name(value) or default


class CountryChoiceField(forms.ChoiceField):
    """Single-select country field. Stores and validates the full country name."""

    default_error_messages = {
        "invalid_choice": "Select a valid country.",
    }

    def __init__(self, *args, **kwargs):
        attrs = {**DEFAULT_COUNTRY_SELECT_ATTRS, **kwargs.pop("attrs", {})}
        kwargs.setdefault("label", "Country")
        kwargs.setdefault("choices", (("", ""), *ALL_COUNTRY_CHOICES))
        kwargs.setdefault("widget", forms.Select(attrs=attrs))
        super().__init__(*args, **kwargs)

    def to_python(self, value):
        return to_country_name(value)

    def validate(self, value):
        # Skip ChoiceField.valid_value (it would reject canonicalised codes); we
        # validate against the canonical name set instead.
        forms.Field.validate(self, value)
        if value and value not in ALL_COUNTRY_NAME_SET:
            raise ValidationError(self.error_messages["invalid_choice"], code="invalid_choice")


class CountryMultipleChoiceField(forms.MultipleChoiceField):
    """Multi-select country field (e.g. eligibility rules). Stores full names.

    Tolerates posted ISO codes by canonicalising each value to its full name,
    so older clients and seed data keep working.
    """

    default_error_messages = {
        "invalid_choice": "Select a valid country.",
    }

    def __init__(self, *args, **kwargs):
        attrs = {
            **DEFAULT_COUNTRY_SELECT_ATTRS,
            "data-s2-placeholder": "Search countries…",
            **kwargs.pop("attrs", {}),
        }
        kwargs.setdefault("widget", forms.SelectMultiple(attrs=attrs))
        super().__init__(*args, **kwargs)

    def to_python(self, value):
        values = super().to_python(value)
        return [name for name in (to_country_name(v) for v in values) if name]

    def validate(self, value):
        if self.required and not value:
            raise ValidationError(self.error_messages["required"], code="required")
        for name in value:
            if name not in ALL_COUNTRY_NAME_SET:
                raise ValidationError(
                    self.error_messages["invalid_choice"], code="invalid_choice"
                )


# Backwards-compatible alias (same behaviour as CountryChoiceField: full ISO list).
RUFORUMCountryChoiceField = CountryChoiceField
