from enum import Enum


class ProvinceBe(Enum):
    antwerpen = "Antwerpen (BE)"
    vlaams_brabant = "Vlaams-Brabant (BE)"
    brabant_wallon = "Brabant-Wallon (BE)"
    bruxelles = "Bruxelles (BE)"
    west_vlaanderen = "West-Vlaanderen (BE)"
    oost_vlaanderen = "Oost-Vlaanderen (BE)"
    hainaut = "Hainaut (BE)"
    limbourg = "Limbourg (BE)"
    liege = "Liege (BE)"
    luxembourg = "Luxembourg (BE)"
    namur = "Namur (BE)"
    not_found = "NOT DECLARED"

    @classmethod
    def get_all_values(cls):
        """Return all possible values for this enum as a list of enum."""
        return [v for v in cls.__dict__.values() if isinstance(v, cls)]

    @classmethod
    def get_all_values_str(cls) -> list[str]:
        """Return all possible values for this enum as a list of str."""
        return [v.value for v in cls.__dict__.values() if isinstance(v, cls)]


class ProvinceFr(Enum):
    @classmethod
    def __new__(cls, value):
        raise NotImplementedError("FR provinces not implemented yet")

    @classmethod
    def get_all_values(cls):
        """Return all possible values for this enum as a list of enum."""
        return [v for v in cls.__dict__.values() if isinstance(v, cls)]

    @classmethod
    def get_all_values_str(cls) -> list[str]:
        """Return all possible values for this enum as a list of str."""
        return [v.value for v in cls.__dict__.values() if isinstance(v, cls)]


class ProvinceNl(Enum):
    noord_holland = "Noord-Holland (NL)"
    zuid_holland = "Zuid-Holland (NL)"
    utrecht = "Utrecht (NL)"
    zeeland = "Zeeland (NL)"
    noord_brabant = "Noord-Brabant (NL)"
    limburg = "Limburg (NL)"
    gelderland = "Gelderland (NL)"
    overijssel = "Overijssel (NL)"
    drenthe = "Drenthe (NL)"
    flevoland = "Flevoland (NL)"
    friesland = "Friesland (NL)"
    groningen = "Groningen (NL)"
    not_found = "NOT DECLARED"

    @classmethod
    def get_all_values(cls):
        """Return all possible values for this enum as a list of enum."""
        return [v for v in cls.__dict__.values() if isinstance(v, cls)]

    @classmethod
    def get_all_values_str(cls) -> list[str]:
        """Return all possible values for this enum as a list of str."""
        return [v.value for v in cls.__dict__.values() if isinstance(v, cls)]


class RegionBe(Enum):
    wallonia = "Wallonie (BE)"
    flanders = "Vlaanderen (BE)"
    bruxelles = "Bruxelles (BE)"
    not_found = "NOT DECLARED"

    @classmethod
    def get_all_values(cls):
        """Return all possible values for this enum as a list of enum."""
        return [v for v in cls.__dict__.values() if isinstance(v, cls)]

    @classmethod
    def get_all_values_str(cls) -> list[str]:
        """Return all possible values for this enum as a list of str."""
        return [v.value for v in cls.__dict__.values() if isinstance(v, cls)]


class RegionFr(Enum):
    @classmethod
    def __new__(cls, value):
        raise NotImplementedError("FR regions not implemented yet")

    @classmethod
    def get_all_values(cls):
        """Return all possible values for this enum as a list of enum."""
        return [v for v in cls.__dict__.values() if isinstance(v, cls)]

    @classmethod
    def get_all_values_str(cls) -> list[str]:
        """Return all possible values for this enum as a list of str."""
        return [v.value for v in cls.__dict__.values() if isinstance(v, cls)]


class RegionNl(Enum):
    western_netherlands = "Western Netherlands (NL)"
    northern_netherlands = "Northern Netherlands (NL)"
    eastern_netherlands = "Eastern Netherlands (NL)"
    southern_netherlands = "Southern Netherlands (NL)"
    not_found = "NOT DECLARED"

    @classmethod
    def get_all_values(cls):
        """Return all possible values for this enum as a list of enum."""
        return [v for v in cls.__dict__.values() if isinstance(v, cls)]

    @classmethod
    def get_all_values_str(cls) -> list[str]:
        """Return all possible values for this enum as a list of str."""
        return [v.value for v in cls.__dict__.values() if isinstance(v, cls)]


class Country(Enum):
    belgium = "BE"
    france = "FR"
    netherlands = "NL"

    @classmethod
    def get_all_values(cls):
        """Return all possible values for this enum as a list of enum."""
        return [v for v in cls.__dict__.values() if isinstance(v, cls)]

    @classmethod
    def get_all_values_str(cls) -> list[str]:
        """Return all possible values for this enum as a list of str."""
        return [v.value for v in cls.__dict__.values() if isinstance(v, cls)]


Region = RegionBe | RegionFr | RegionNl
Province = ProvinceBe | ProvinceFr | ProvinceNl


def get_province_from_postal_code(postal_code: str | None, country: Country) -> Province | None:
    """Get the province from a postal code for the given country.

    Sources:
    - BE: https://www.spotzi.com/en/data-catalog/categories/postal-codes/belgium/
    - NL: https://nl.wikipedia.org/wiki/Postcodes_in_Nederland
    """
    if not postal_code or postal_code in {"****", "0999"}:
        return None

    if country == Country.belgium:
        try:
            postal_code_int = int(postal_code.strip())
        except ValueError:
            return None
        if 1000 <= postal_code_int <= 1299:
            return ProvinceBe.bruxelles
        elif 1300 <= postal_code_int <= 1499:
            return ProvinceBe.brabant_wallon
        elif 1500 <= postal_code_int <= 1999:
            return ProvinceBe.vlaams_brabant
        elif 2000 <= postal_code_int <= 2999:
            return ProvinceBe.antwerpen
        elif 3000 <= postal_code_int <= 3499:
            return ProvinceBe.vlaams_brabant
        elif 3500 <= postal_code_int <= 3999:
            return ProvinceBe.limbourg
        elif 4000 <= postal_code_int <= 4999:
            return ProvinceBe.liege
        elif 5000 <= postal_code_int <= 5999:
            return ProvinceBe.namur
        elif 6000 <= postal_code_int <= 6599:
            return ProvinceBe.hainaut
        elif 6600 <= postal_code_int <= 6999:
            return ProvinceBe.luxembourg
        elif 7000 <= postal_code_int <= 7999:
            return ProvinceBe.hainaut
        elif 8000 <= postal_code_int <= 8999:
            return ProvinceBe.west_vlaanderen
        elif 9000 <= postal_code_int <= 9999:
            return ProvinceBe.oost_vlaanderen
        else:
            return None

    elif country == Country.netherlands:
        try:
            postal_prefix = int(postal_code[:4])
        except (ValueError, TypeError):
            return None
        if (
            1000 <= postal_prefix <= 1299
            or 1380 <= postal_prefix <= 1384
            or 1398 <= postal_prefix <= 1425
            or 1430 <= postal_prefix <= 2158
            or postal_prefix in {1394, 2165}
        ):
            return ProvinceNl.noord_holland
        elif (
            2200 <= postal_prefix <= 3299
            or 1428 <= postal_prefix <= 1429
            or 2159 <= postal_prefix <= 2164
            or 2170 <= postal_prefix <= 3381
            or 3465 <= postal_prefix <= 3466
            or 4126 <= postal_prefix <= 4129
            or 4200 <= postal_prefix <= 4209
            or 4220 <= postal_prefix <= 4229
            or 4240 <= postal_prefix <= 4241
            or postal_prefix in {2166, 4213}
        ):
            return ProvinceNl.zuid_holland
        elif (
            1390 <= postal_prefix <= 1393
            or 1426 <= postal_prefix <= 1427
            or 3382 <= postal_prefix <= 3464
            or 3467 <= postal_prefix <= 3769
            or 3795 <= postal_prefix <= 3836
            or 3900 <= postal_prefix <= 3924
            or 3926 <= postal_prefix <= 3999
            or 4120 <= postal_prefix <= 4125
            or 4130 <= postal_prefix <= 4146
            or 4163 <= postal_prefix <= 4169
            or 4230 <= postal_prefix <= 4239
            or 4242 <= postal_prefix <= 4249
            or postal_prefix == 1396
        ):
            return ProvinceNl.utrecht
        elif 4300 <= postal_prefix <= 4599 or 4672 <= postal_prefix <= 4679 or 4682 <= postal_prefix <= 4699:
            return ProvinceNl.zeeland
        elif (
            4250 <= postal_prefix <= 4299
            or 4600 <= postal_prefix <= 4671
            or 4680 <= postal_prefix <= 4681
            or 4700 <= postal_prefix <= 5299
            or 5340 <= postal_prefix <= 5765
            or 5820 <= postal_prefix <= 5846
            or 6020 <= postal_prefix <= 6029
        ):
            return ProvinceNl.noord_brabant
        elif (
            5766 <= postal_prefix <= 5817
            or 5850 <= postal_prefix <= 6019
            or 6030 <= postal_prefix <= 6499
            or 6584 <= postal_prefix <= 6599
        ):
            return ProvinceNl.limburg
        elif (
            3770 <= postal_prefix <= 3794
            or 3837 <= postal_prefix <= 3888
            or 4000 <= postal_prefix <= 4119
            or 4147 <= postal_prefix <= 4162
            or 4170 <= postal_prefix <= 4199
            or 4211 <= postal_prefix <= 4212
            or 4214 <= postal_prefix <= 4219
            or 5300 <= postal_prefix <= 5335
            or 6500 <= postal_prefix <= 6583
            or 6600 <= postal_prefix <= 7399
            or 8050 <= postal_prefix <= 8054
            or 8070 <= postal_prefix <= 8099
            or 8160 <= postal_prefix <= 8195
            or postal_prefix in {3925, 7439}
        ):
            return ProvinceNl.gelderland
        elif (
            7400 <= postal_prefix <= 7438
            or 7440 <= postal_prefix <= 7739
            or 7767 <= postal_prefix <= 7799
            or 7950 <= postal_prefix <= 7955
            or 8000 <= postal_prefix <= 8049
            or 8055 <= postal_prefix <= 8069
            or 8100 <= postal_prefix <= 8159
            or 8196 <= postal_prefix <= 8199
            or 8260 <= postal_prefix <= 8299
            or 8323 <= postal_prefix <= 8349
            or 8355 <= postal_prefix <= 8379
        ):
            return ProvinceNl.overijssel
        elif (
            7740 <= postal_prefix <= 7766
            or 7800 <= postal_prefix <= 7949
            or 7956 <= postal_prefix <= 7999
            or 8350 <= postal_prefix <= 8354
            or 8380 <= postal_prefix <= 8387
            or 9300 <= postal_prefix <= 9349
            or 9400 <= postal_prefix <= 9478
            or 9480 <= postal_prefix <= 9499
            or 9510 <= postal_prefix <= 9539
            or 9570 <= postal_prefix <= 9579
            or 9654 <= postal_prefix <= 9659
            or 9760 <= postal_prefix <= 9769
            or postal_prefix in {9749, 9564}
        ):
            return ProvinceNl.drenthe
        elif (
            1300 <= postal_prefix <= 1379
            or 3890 <= postal_prefix <= 3899
            or 8200 <= postal_prefix <= 8259
            or 8300 <= postal_prefix <= 8322
        ):
            return ProvinceNl.flevoland
        elif 8388 <= postal_prefix <= 9299 or 9850 <= postal_prefix <= 9859 or 9870 <= postal_prefix <= 9879:
            return ProvinceNl.friesland
        elif (
            9350 <= postal_prefix <= 9399
            or 9500 <= postal_prefix <= 9509
            or 9540 <= postal_prefix <= 9563
            or 9565 <= postal_prefix <= 9569
            or 9580 <= postal_prefix <= 9653
            or 9660 <= postal_prefix <= 9748
            or 9750 <= postal_prefix <= 9759
            or 9770 <= postal_prefix <= 9849
            or 9860 <= postal_prefix <= 9869
            or 9880 <= postal_prefix <= 9999
            or postal_prefix == 9479
        ):
            return ProvinceNl.groningen
        else:
            return None

    else:
        raise NotImplementedError(f"Country {country.value} not implemented yet.")


PROVINCE_TO_REGION_MAPPER_BE = {
    ProvinceBe.antwerpen: RegionBe.flanders,
    ProvinceBe.limbourg: RegionBe.flanders,
    ProvinceBe.oost_vlaanderen: RegionBe.flanders,
    ProvinceBe.west_vlaanderen: RegionBe.flanders,
    ProvinceBe.vlaams_brabant: RegionBe.flanders,
    ProvinceBe.bruxelles: RegionBe.bruxelles,
    ProvinceBe.brabant_wallon: RegionBe.wallonia,
    ProvinceBe.hainaut: RegionBe.wallonia,
    ProvinceBe.liege: RegionBe.wallonia,
    ProvinceBe.namur: RegionBe.wallonia,
    ProvinceBe.luxembourg: RegionBe.wallonia,
}

PROVINCE_TO_REGION_MAPPER_NL = {
    ProvinceNl.groningen: RegionNl.northern_netherlands,
    ProvinceNl.friesland: RegionNl.northern_netherlands,
    ProvinceNl.drenthe: RegionNl.northern_netherlands,
    ProvinceNl.noord_holland: RegionNl.western_netherlands,
    ProvinceNl.zuid_holland: RegionNl.western_netherlands,
    ProvinceNl.utrecht: RegionNl.western_netherlands,
    ProvinceNl.zeeland: RegionNl.western_netherlands,
    ProvinceNl.noord_brabant: RegionNl.southern_netherlands,
    ProvinceNl.limburg: RegionNl.southern_netherlands,
    ProvinceNl.overijssel: RegionNl.eastern_netherlands,
    ProvinceNl.gelderland: RegionNl.eastern_netherlands,
    ProvinceNl.flevoland: RegionNl.eastern_netherlands,
    ProvinceNl.not_found: RegionNl.not_found,
}


def get_region_from_province(province: Province | None, country: Country) -> Region | None:
    """Get the region from the province for the given country."""
    if not province:
        return None

    if country == Country.belgium:
        assert isinstance(province, ProvinceBe)
        return PROVINCE_TO_REGION_MAPPER_BE.get(province)

    if country == Country.netherlands:
        assert isinstance(province, ProvinceNl)
        return PROVINCE_TO_REGION_MAPPER_NL.get(province)

    return None


def get_all_regions(countries: list[Country] | list[str]) -> list[Region]:
    """Get all regions for a list of countries. Countires can be a list of Country enums or a list of str."""
    all_regions = []
    if not countries:
        return all_regions

    if isinstance(countries[0], str):
        countries = [Country(country) for country in countries]

    if Country.belgium in countries:
        all_regions.extend(RegionBe.get_all_values())
    if Country.france in countries:
        all_regions.extend(RegionFr.get_all_values())
    if Country.netherlands in countries:
        all_regions.extend(RegionNl.get_all_values())
    return all_regions


def get_all_regions_str(countries: list[Country] | list[str]) -> list[str]:
    """Get all regions for a list of countries. Countries can be a list of Country enums or a list of str."""
    all_regions = []
    if not countries:
        return all_regions

    if isinstance(countries[0], str):
        countries = [Country(country) for country in countries]

    for country in countries:
        if country == Country.belgium:
            all_regions.extend(RegionBe.get_all_values_str())
        elif country == Country.france:
            all_regions.extend(RegionFr.get_all_values_str())
        elif country == Country.netherlands:
            all_regions.extend(RegionNl.get_all_values_str())
        else:
            raise ValueError(f"Unknown country: `{country}`")

    return all_regions


def get_all_provinces(countries: list[Country] | list[str]) -> list[Province]:
    """Get all provinces for a list of countries. Countries can be a list of Country enums or a list of str."""
    all_provinces = []
    if not countries:
        return all_provinces

    if isinstance(countries[0], str):
        countries = [Country(country) for country in countries]

    if Country.belgium in countries:
        all_provinces.extend(ProvinceBe.get_all_values())
    if Country.france in countries:
        all_provinces.extend(ProvinceFr.get_all_values())
    if Country.netherlands in countries:
        all_provinces.extend(ProvinceNl.get_all_values())

    return all_provinces


def get_all_provinces_str(countries: list[Country] | list[str]) -> list[str]:
    """Get all provinces for a list of countries. Countries can be a list of Country enums or a list of str."""
    all_provinces = []
    if not countries:
        return all_provinces

    if isinstance(countries[0], str):
        countries = [Country(country) for country in countries]

    for country in countries:
        if country == Country.belgium:
            all_provinces.extend(ProvinceBe.get_all_values_str())
        elif country == Country.france:
            all_provinces.extend(ProvinceFr.get_all_values_str())
        elif country == Country.netherlands:
            all_provinces.extend(ProvinceNl.get_all_values_str())
        else:
            raise ValueError(f"Unknown country: `{country}`")

    return all_provinces


def coutries_to_enums(coutry_list: list[str]) -> list[Country]:
    """Format a list of countries as a string to a list of Country enums."""
    return [Country(country) for country in coutry_list]


def provinces_to_enums(province_list: list[str]) -> list[Province]:
    """Format a list of provinces as a string to a list of Province enums."""
    new_list = []
    for province in province_list:
        if province in ProvinceBe.get_all_values_str():
            province_enum = ProvinceBe(province)
        elif province in ProvinceFr.get_all_values_str():
            province_enum = ProvinceFr(province)
        elif province in ProvinceNl.get_all_values_str():
            province_enum = ProvinceNl(province)
        else:
            raise ValueError(f"Unknown province: `{province}`")
        new_list.append(province_enum)
    return new_list


def regions_to_enums(region_list: list[str]) -> list[Region]:
    """Format a list of regions as a string to a list of Region enums."""
    new_list = []
    for region in region_list:
        if region in RegionBe.get_all_values_str():
            region_enum = RegionBe(region)
        elif region in RegionFr.get_all_values_str():
            region_enum = RegionFr(region)
        elif region in RegionNl.get_all_values_str():
            region_enum = RegionNl(region)
        else:
            raise ValueError(f"Unknown region: `{region}`")
        new_list.append(region_enum)
    return new_list
