"""Explicit English-language market scope for country-wide lead generation."""

from __future__ import annotations

import re
from dataclasses import dataclass
from types import MappingProxyType
from typing import Final, Mapping

from .errors import InvalidConfiguration

_COUNTRY_CODE = re.compile(r"^[A-Z]{2}$")
_CALLING_CODE = re.compile(r"^[1-9][0-9]{0,3}$")
_FEATURE_ID = re.compile(r"^[a-z0-9][a-z0-9-]{0,127}$")


@dataclass(frozen=True, slots=True)
class GeoBounds:
    """Closed WGS84 bounding box represented as integer E7 coordinates."""

    min_longitude_e7: int
    min_latitude_e7: int
    max_longitude_e7: int
    max_latitude_e7: int

    def __post_init__(self) -> None:
        for field_name in (
            "min_longitude_e7",
            "min_latitude_e7",
            "max_longitude_e7",
            "max_latitude_e7",
        ):
            value = getattr(self, field_name)
            if isinstance(value, bool) or not isinstance(value, int):
                raise InvalidConfiguration(f"{field_name} must be an integer")
        if not -1_800_000_000 <= self.min_longitude_e7 < self.max_longitude_e7 <= 1_800_000_000:
            raise InvalidConfiguration("longitude bounds are invalid")
        if not -900_000_000 <= self.min_latitude_e7 < self.max_latitude_e7 <= 900_000_000:
            raise InvalidConfiguration("latitude bounds are invalid")

    def contains(self, longitude_e7: int, latitude_e7: int) -> bool:
        return (
            self.min_longitude_e7 <= longitude_e7 <= self.max_longitude_e7
            and self.min_latitude_e7 <= latitude_e7 <= self.max_latitude_e7
        )

    def as_mapping(self) -> dict[str, int]:
        return {
            "min_longitude_e7": self.min_longitude_e7,
            "min_latitude_e7": self.min_latitude_e7,
            "max_longitude_e7": self.max_longitude_e7,
            "max_latitude_e7": self.max_latitude_e7,
        }


@dataclass(frozen=True, slots=True)
class EnglishMarket:
    """One sovereign English-language market and its source-resolution metadata."""

    country_code: str
    display_name: str
    calling_code: str
    source_feature_id: str | None = None
    source_bounds: GeoBounds | None = None

    def __post_init__(self) -> None:
        if not isinstance(self.country_code, str) or not _COUNTRY_CODE.fullmatch(self.country_code):
            raise InvalidConfiguration("country_code must be uppercase ISO alpha-2")
        if (
            not isinstance(self.display_name, str)
            or not self.display_name.strip()
            or len(self.display_name.strip()) > 128
        ):
            raise InvalidConfiguration("display_name must be a bounded nonblank string")
        object.__setattr__(self, "display_name", self.display_name.strip())
        if not isinstance(self.calling_code, str) or not _CALLING_CODE.fullmatch(self.calling_code):
            raise InvalidConfiguration("calling_code must contain 1 to 4 digits")
        if self.source_feature_id is not None:
            if not isinstance(self.source_feature_id, str) or not _FEATURE_ID.fullmatch(
                self.source_feature_id
            ):
                raise InvalidConfiguration("source_feature_id has an invalid shape")
        if self.source_bounds is not None and not isinstance(self.source_bounds, GeoBounds):
            raise InvalidConfiguration("source_bounds must be GeoBounds or null")
        if self.source_bounds is not None and self.source_feature_id is None:
            raise InvalidConfiguration("source_bounds require a source_feature_id override")

    @property
    def requires_country_filter(self) -> bool:
        return self.source_bounds is not None


def _e7(value: float) -> int:
    return round(value * 10_000_000)


def _bounds(min_lon: float, min_lat: float, max_lon: float, max_lat: float) -> GeoBounds:
    return GeoBounds(_e7(min_lon), _e7(min_lat), _e7(max_lon), _e7(max_lat))


_MARKETS: Final = (
    EnglishMarket(
        "AG",
        "Antigua and Barbuda",
        "1",
        "central-america",
        _bounds(-62.348260, 16.931970, -61.667592, 17.727688),
    ),
    EnglishMarket("AU", "Australia", "61"),
    EnglishMarket("BS", "Bahamas", "1"),
    EnglishMarket(
        "BB",
        "Barbados",
        "1",
        "central-america",
        _bounds(-59.654205, 13.051174, -59.426910, 13.344550),
    ),
    EnglishMarket("BZ", "Belize", "501"),
    EnglishMarket("BW", "Botswana", "267"),
    EnglishMarket("BI", "Burundi", "257"),
    EnglishMarket("CM", "Cameroon", "237"),
    EnglishMarket("CA", "Canada", "1"),
    EnglishMarket(
        "DM",
        "Dominica",
        "1",
        "central-america",
        _bounds(-61.488922, 15.201809, -61.249257, 15.633857),
    ),
    EnglishMarket("SZ", "Eswatini", "268"),
    EnglishMarket("FJ", "Fiji", "679"),
    EnglishMarket("GM", "Gambia", "220"),
    EnglishMarket("GH", "Ghana", "233"),
    EnglishMarket(
        "GD",
        "Grenada",
        "1",
        "central-america",
        _bounds(-61.790517, 12.002834, -61.421620, 12.529731),
    ),
    EnglishMarket("GY", "Guyana", "592"),
    EnglishMarket("IN", "India", "91"),
    EnglishMarket("IE", "Ireland", "353"),
    EnglishMarket("JM", "Jamaica", "1"),
    EnglishMarket("KE", "Kenya", "254"),
    EnglishMarket("KI", "Kiribati", "686"),
    EnglishMarket("LS", "Lesotho", "266"),
    EnglishMarket("LR", "Liberia", "231"),
    EnglishMarket("MW", "Malawi", "265"),
    EnglishMarket("MT", "Malta", "356"),
    EnglishMarket("MH", "Marshall Islands", "692", "marshall-islands"),
    EnglishMarket("MU", "Mauritius", "230"),
    EnglishMarket("FM", "Micronesia", "691"),
    EnglishMarket("NA", "Namibia", "264"),
    EnglishMarket("NR", "Nauru", "674"),
    EnglishMarket("NZ", "New Zealand", "64"),
    EnglishMarket("NG", "Nigeria", "234"),
    EnglishMarket("PK", "Pakistan", "92"),
    EnglishMarket("PW", "Palau", "680"),
    EnglishMarket("PG", "Papua New Guinea", "675"),
    EnglishMarket("PH", "Philippines", "63"),
    EnglishMarket("RW", "Rwanda", "250"),
    EnglishMarket(
        "KN",
        "Saint Kitts and Nevis",
        "1",
        "central-america",
        _bounds(-62.861073, 17.100531, -62.536773, 17.415839),
    ),
    EnglishMarket(
        "LC",
        "Saint Lucia",
        "1",
        "central-america",
        _bounds(-61.078521, 13.714667, -60.882965, 14.111884),
    ),
    EnglishMarket(
        "VC",
        "Saint Vincent and the Grenadines",
        "1",
        "central-america",
        _bounds(-61.459828, 12.585150, -61.123931, 13.380764),
    ),
    EnglishMarket("WS", "Samoa", "685"),
    EnglishMarket("SC", "Seychelles", "248"),
    EnglishMarket("SL", "Sierra Leone", "232"),
    EnglishMarket(
        "SG",
        "Singapore",
        "65",
        "malaysia-singapore-brunei",
        _bounds(103.640391, 1.264309, 104.003429, 1.448635),
    ),
    EnglishMarket("SB", "Solomon Islands", "677"),
    EnglishMarket("ZA", "South Africa", "27"),
    EnglishMarket("SS", "South Sudan", "211"),
    EnglishMarket("SD", "Sudan", "249"),
    EnglishMarket("TZ", "Tanzania", "255"),
    EnglishMarket("TO", "Tonga", "676"),
    EnglishMarket(
        "TT",
        "Trinidad and Tobago",
        "1",
        "central-america",
        _bounds(-61.928700, 10.042060, -60.522084, 11.351060),
    ),
    EnglishMarket("TV", "Tuvalu", "688"),
    EnglishMarket("UG", "Uganda", "256"),
    EnglishMarket("GB", "United Kingdom", "44"),
    EnglishMarket("US", "United States", "1"),
    EnglishMarket("VU", "Vanuatu", "678", "vanuatu"),
    EnglishMarket("ZM", "Zambia", "260"),
    EnglishMarket("ZW", "Zimbabwe", "263"),
)

if len(_MARKETS) != 58:
    raise RuntimeError("English market registry must contain 58 sovereign states")

_by_code = {market.country_code: market for market in _MARKETS}
if len(_by_code) != len(_MARKETS):
    raise RuntimeError("English market country codes must be unique")

ENGLISH_MARKETS: Final[Mapping[str, EnglishMarket]] = MappingProxyType(_by_code)
ENGLISH_MARKET_CODES: Final[tuple[str, ...]] = tuple(sorted(_by_code))
_MARKET_ALIASES: Final[Mapping[str, str]] = MappingProxyType(
    {
        "UK": "GB",
        "USA": "US",
    }
)


def get_english_market(country_code: str) -> EnglishMarket:
    """Resolve one configured market by ISO code or accepted operator alias."""

    if not isinstance(country_code, str) or not country_code.strip():
        raise InvalidConfiguration("country code must be nonblank")
    normalized = country_code.strip().upper()
    normalized = _MARKET_ALIASES.get(normalized, normalized)
    try:
        return ENGLISH_MARKETS[normalized]
    except KeyError:
        raise InvalidConfiguration(
            f"unsupported English-market country code: {normalized}"
        ) from None


def parse_market_selection(raw: str | None) -> tuple[EnglishMarket, ...]:
    """Parse `all` or a comma-separated country-code selection deterministically."""

    if raw is None or not raw.strip() or raw.strip().casefold() == "all":
        return tuple(ENGLISH_MARKETS[code] for code in ENGLISH_MARKET_CODES)
    parts = [part.strip() for part in raw.split(",")]
    if any(not part for part in parts):
        raise InvalidConfiguration("country selection contains an empty item")
    markets = {get_english_market(part).country_code: get_english_market(part) for part in parts}
    return tuple(markets[code] for code in sorted(markets))


__all__ = [
    "ENGLISH_MARKET_CODES",
    "ENGLISH_MARKETS",
    "EnglishMarket",
    "GeoBounds",
    "get_english_market",
    "parse_market_selection",
]
