"""
Shared datastructures and constants.
"""


from dataclasses import dataclass
from ipaddress import IPv4Address, IPv4Network
from typing import Dict, List, Final, Any, Union


@dataclass(frozen=True)
class IPv4Range:
    start: IPv4Address
    end: IPv4Address

    @classmethod
    def from_str(cls, start: str, end: str) -> 'IPv4Range':
        return cls(start=IPv4Address(start), end=IPv4Address(end))

    @classmethod
    def from_mask_str(cls, net: str, strict=True) -> 'IPv4Range':
        prefix: IPv4Network = IPv4Network(net, strict=strict)
        return cls(start=prefix.network_address, end=prefix.broadcast_address)

    @classmethod
    def from_range_str(cls, net: str) -> 'IPv4Range':
        parts: List[str] = net.split("-", maxsplit=1)
        return cls.from_str(parts[0].strip(), parts[1].strip())

    def __post_init__(self) -> None:
        if self.start > self.end:
            raise ValueError(str(self))

    def __str__(self) -> str:
        return f"{self.start}-{self.end}"

    def __lt__(self, other: 'IPv4Range') -> bool:
        return (self.start, self.end) < (other.start, other.end)

    def __contains__(self, other: Union[IPv4Address, 'IPv4Range']) -> bool:
        if isinstance(other, IPv4Address):
            return self.start <= other <= self.end
        return self.start <= other.start and other.end <= self.end


@dataclass(frozen=True)
class Entry:
    address_range: IPv4Range
    country: str
    source: str

    @classmethod
    def from_dict(cls, dct: Dict[str, Any]) -> 'Entry':
        return cls(address_range=IPv4Range.from_str(str(dct["start"]), str(dct["end"])),
                   country=str(dct["country"]),
                   source=str(dct["source"]))

    def to_dict(self) -> Dict[str, Any]:
        return {
            "start": str(self.address_range.start),
            "end": str(self.address_range.end),
            "country": self.country,
            "source": self.source,
        }


@dataclass(frozen=True)
class Transfer:
    address_range: IPv4Range
    source: str
    recipient: str
    timestamp: int  # milliseconds

    @classmethod
    def from_dict(cls, dct: Dict[str, Any]) -> 'Transfer':
        return cls(address_range=IPv4Range.from_str(str(dct["start"]), str(dct["end"])),
                   source=str(dct["source"]),
                   recipient=str(dct["recipient"]),
                   timestamp=int(dct["timestamp"]))

    def to_dict(self) -> Dict[str, Any]:
        return {
            "start": str(self.address_range.start),
            "end": str(self.address_range.end),
            "source": self.source,
            "recipient": self.recipient,
            "timestamp": self.timestamp,
        }


@dataclass(frozen=True)
class Geofeed:
    address_range: IPv4Range
    geofeed: str  # url

    @classmethod
    def from_dict(cls, dct: Dict[str, Any]) -> 'Geofeed':
        return cls(address_range=IPv4Range.from_str(str(dct["start"]), str(dct["end"])),
                   geofeed=str(dct["geofeed"]))

    def to_dict(self) -> Dict[str, Any]:
        return {
            "start": str(self.address_range.start),
            "end": str(self.address_range.end),
            "geofeed": self.geofeed,
        }


REGISTRIES: Final[Dict[str, str]] = {
    "AFRINIC": "AFRINIC",
    "APNIC": "APNIC",
    "ARIN": "ARIN",
    "IANA": "IANA",
    "LACNIC": "LACNIC",
    "RESERVED": "RESERVED",
    "RIPE": "RIPE",
    "RIPENCC": "RIPE",
    "RIPE NCC": "RIPE",
}

RESERVED_NETWORKS: Final[List[str]] = [
    "0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/29",
    "192.0.0.170/31", "192.0.2.0/24", "192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24",
    "224.0.0.0/4", "240.0.0.0/4", "255.255.255.255/32",
]

# https://www.destatis.de/Europa/EN/Country/Country-Codes.html?nn=218284#AnkerEU
EU_MEMBERS: Final[List[str]] = [
    "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT",
    "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "EU",
]

COUNTRY_ALIASES: Final[Dict[str, str]] = {
    "UK": "GB",
}

# http://download.geonames.org/export/dump/readme.txt
CONTINENTS: Final[Dict[str, Dict[str, str]]] = {
    "AF": {"geoname_id": "6255146", "continent_code": "AF", "continent_name": "Africa"},
    "AS": {"geoname_id": "6255147", "continent_code": "AS", "continent_name": "Asia"},
    "EU": {"geoname_id": "6255148", "continent_code": "EU", "continent_name": "Europe"},
    "NA": {"geoname_id": "6255149", "continent_code": "NA", "continent_name": "North America"},
    "OC": {"geoname_id": "6255151", "continent_code": "OC", "continent_name": "Oceania"},
    "SA": {"geoname_id": "6255150", "continent_code": "SA", "continent_name": "South America"},
    "AN": {"geoname_id": "6255152", "continent_code": "AN", "continent_name": "Antarctica"},
}

LOCATIONS: Final[List[Dict[str, str]]] = [
    {"geoname_id": "6695072", "continent_code": "EU", "continent_name": "Europe",
     "country_iso_code": "EU", "country_name": "European Union"},
]

REGISTRY_CONTINENTS: Final[Dict[str, str]] = {
    "AFRINIC": "AF",
    "APNIC": "AS",
    "ARIN": "NA",
    "LACNIC": "SA",
    "RIPE": "EU",
    "RESERVED": "",
}