"""IP Protocols"""

from operator import itemgetter
from typing import Any, Tuple

from netports import helpers as h
from netports.exceptions import NetportsValueError
from netports.ports import snumbers
from netports.range import Range
from netports.static import BRIEF_ALL_I
from netports.types_ import LInt, LStr, DAny, DiAny, SStr, SInt, LTIntStr

IP_NUMBERS: DiAny = {
    0: {"number": 0, "name": "hopopt", "description": "IPv6 Hop-by-Hop Option, RFC 8200"},
    1: {"number": 1, "name": "icmp", "description": "Internet Control Message Protocol, RFC 792"},
    2: {"number": 2, "name": "igmp", "description": "Internet Group Management Protocol, RFC 1112"},
    3: {"number": 3, "name": "ggp", "description": "Gateway-to-Gateway Protocol, RFC 823"},
    4: {"number": 4, "name": "ip-in-ip", "description": "IP in IP (encapsulation), RFC 2003"},
    5: {"number": 5, "name": "st", "description": "Internet Stream Protocol, RFC 1190, RFC 1819"},
    6: {"number": 6, "name": "tcp", "description": "Transmission Control Protocol, RFC 793"},
    7: {"number": 7, "name": "cbt", "description": "Core-based trees, RFC 2189"},
    8: {"number": 8, "name": "egp", "description": "Exterior Gateway Protocol, RFC 888"},
    9: {"number": 9, "name": "igp", "description": "Interior Gateway Protocol (Cisco IGRP)"},
    10: {"number": 10, "name": "bbn-rcc-mon", "description": "BBN RCC Monitoring"},
    11: {"number": 11, "name": "nvp-ii", "description": "Network Voice Protocol, RFC 741"},
    12: {"number": 12, "name": "pup", "description": "Xerox PUP"},
    13: {"number": 13, "name": "argus", "description": "ARGUS"},
    14: {"number": 14, "name": "emcon", "description": "EMCON"},
    15: {"number": 15, "name": "xnet", "description": "Cross Net Debugger, IEN 158"},
    16: {"number": 16, "name": "chaos", "description": "Chaos"},
    17: {"number": 17, "name": "udp", "description": "User Datagram Protocol, RFC 768"},
    18: {"number": 18, "name": "mux", "description": "Multiplexing, IEN 90"},
    19: {"number": 19, "name": "dcn-meas", "description": "DCN Measurement Subsystems"},
    20: {"number": 20, "name": "hmp", "description": "Host Monitoring Protocol, RFC 869"},
    21: {"number": 21, "name": "prm", "description": "Packet Radio Measurement"},
    22: {"number": 22, "name": "xns-idp", "description": "XEROX NS IDP"},
    23: {"number": 23, "name": "trunk-1", "description": "Trunk-1"},
    24: {"number": 24, "name": "trunk-2", "description": "Trunk-2"},
    25: {"number": 25, "name": "leaf-1", "description": "Leaf-1"},
    26: {"number": 26, "name": "leaf-2", "description": "Leaf-2"},
    27: {"number": 27, "name": "rdp", "description": "Reliable Data Protocol, RFC 908"},
    28: {
        "number": 28,
        "name": "irtp",
        "description": "Internet Reliable Transaction Protocol, RFC 938",
    },
    29: {"number": 29, "name": "iso-tp4", "description": "ISO Transport Protocol Class 4, RFC 905"},
    30: {"number": 30, "name": "netblt", "description": "Bulk Data Transfer Protocol, RFC 998"},
    31: {"number": 31, "name": "mfe-nsp", "description": "MFE Network Services Protocol"},
    32: {"number": 32, "name": "merit-inp", "description": "MERIT Internodal Protocol"},
    33: {
        "number": 33,
        "name": "dccp",
        "description": "Datagram Congestion Control Protocol, RFC 4340",
    },
    34: {"number": 34, "name": "3pc", "description": "Third Party Connect Protocol"},
    35: {
        "number": 35,
        "name": "idpr",
        "description": "Inter-Domain Policy Routing Protocol, RFC 1479",
    },
    36: {"number": 36, "name": "xtp", "description": "Xpress Transport Protocol"},
    37: {"number": 37, "name": "ddp", "description": "Datagram Delivery Protocol"},
    38: {
        "number": 38,
        "name": "idpr-cmtp",
        "description": "IDPR Control Message Transport Protocol",
    },
    39: {"number": 39, "name": "tp++", "description": "TP++ Transport Protocol"},
    40: {"number": 40, "name": "il", "description": "IL Transport Protocol"},
    41: {
        "number": 41,
        "name": "ipv6",
        "description": "IPv6 Encapsulation (6to4 and 6in4), RFC 2473",
    },
    42: {"number": 42, "name": "sdrp", "description": "Source Demand Routing Protocol, RFC 1940"},
    43: {"number": 43, "name": "ipv6-route", "description": "Routing Header for IPv6, RFC 8200"},
    44: {"number": 44, "name": "ipv6-frag", "description": "Fragment Header for IPv6, RFC 8200"},
    45: {"number": 45, "name": "idrp", "description": "Inter-Domain Routing Protocol"},
    46: {"number": 46, "name": "rsvp", "description": "Resource Reservation Protocol, RFC 2205"},
    47: {
        "number": 47,
        "name": "gre",
        "description": "Generic Routing Encapsulation, RFC 2784, RFC 2890",
    },
    48: {"number": 48, "name": "dsr", "description": "Dynamic Source Routing Protocol, RFC 4728"},
    49: {"number": 49, "name": "bna", "description": "Burroughs Network Architecture"},
    50: {"number": 50, "name": "esp", "description": "Encapsulating Security Payload, RFC 4303"},
    51: {"number": 51, "name": "ah", "description": "Authentication Header, RFC 4302"},
    52: {
        "number": 52,
        "name": "i-nlsp",
        "description": "Integrated Net Layer Security Protocol, TUBA",
    },
    53: {"number": 53, "name": "swipe", "description": "SwIPe, RFC 5237"},
    54: {"number": 54, "name": "narp", "description": "NBMA Address Resolution Protocol, RFC 1735"},
    55: {"number": 55, "name": "mobile", "description": "IP Mobility (Min Encap), RFC 2004"},
    56: {
        "number": 56,
        "name": "tlsp",
        "description": "Transport Layer Security Protocol (using Kryptonet key management)",
    },
    57: {
        "number": 57,
        "name": "skip",
        "description": "Simple Key-Management for Internet Protocol, RFC 2356",
    },
    58: {"number": 58, "name": "ipv6-icmp", "description": "ICMP for IPv6, RFC 4443, RFC 4884"},
    59: {"number": 59, "name": "ipv6-nonxt", "description": "No Next Header for IPv6, RFC 8200"},
    60: {
        "number": 60,
        "name": "ipv6-opts",
        "description": "Destination Options for IPv6, RFC 8200",
    },
    61: {"number": 61, "name": "any", "description": "host internal protocol"},
    62: {"number": 62, "name": "cftp", "description": "CFTP"},
    63: {"number": 63, "name": "any", "description": "local network"},
    64: {"number": 64, "name": "sat-expak", "description": "SATNET and Backroom EXPAK"},
    65: {"number": 65, "name": "kryptolan", "description": "Kryptolan"},
    66: {"number": 66, "name": "rvd", "description": "MIT Remote Virtual Disk Protocol"},
    67: {"number": 67, "name": "ippc", "description": "Internet Pluribus Packet Core"},
    68: {"number": 68, "name": "any", "description": "distributed file system"},
    69: {"number": 69, "name": "sat-mon", "description": "SATNET Monitoring"},
    70: {"number": 70, "name": "visa", "description": "VISA Protocol"},
    71: {"number": 71, "name": "ipcu", "description": "Internet Packet Core Utility"},
    72: {"number": 72, "name": "cpnx", "description": "Computer Protocol Network Executive"},
    73: {"number": 73, "name": "cphb", "description": "Computer Protocol Heart Beat"},
    74: {"number": 74, "name": "wsn", "description": "Wang Span Network"},
    75: {"number": 75, "name": "pvp", "description": "Packet Video Protocol"},
    76: {"number": 76, "name": "br-sat-mon", "description": "Backroom SATNET Monitoring"},
    77: {"number": 77, "name": "sun-nd", "description": "SUN ND PROTOCOL-Temporary"},
    78: {"number": 78, "name": "wb-mon", "description": "WIDEBAND Monitoring"},
    79: {"number": 79, "name": "wb-expak", "description": "WIDEBAND EXPAK"},
    80: {
        "number": 80,
        "name": "iso-ip",
        "description": "International Organization for Standardization Internet Protocol",
    },
    81: {
        "number": 81,
        "name": "vmtp",
        "description": "Versatile Message Transaction Protocol, RFC 1045",
    },
    82: {
        "number": 82,
        "name": "secure-vmtp",
        "description": "Secure Versatile Message Transaction Protocol, RFC 1045",
    },
    83: {"number": 83, "name": "vines", "description": "VINES"},
    84: {"number": 84, "name": "ttp", "description": "TTP"},
    85: {"number": 85, "name": "nsfnet-igp", "description": "NSFNET-IGP"},
    86: {"number": 86, "name": "dgp", "description": "Dissimilar Gateway Protocol"},
    87: {"number": 87, "name": "tcf", "description": "TCF"},
    88: {"number": 88, "name": "eigrp", "description": "EIGRP, Informational RFC 7868"},
    89: {"number": 89, "name": "ospf", "description": "Open Shortest Path First, RFC 2328"},
    90: {"number": 90, "name": "sprite-rpc", "description": "Sprite RPC Protocol"},
    91: {"number": 91, "name": "larp", "description": "Locus Address Resolution Protocol"},
    92: {"number": 92, "name": "mtp", "description": "Multicast Transport Protocol"},
    93: {"number": 93, "name": "ax.25", "description": "AX.25"},
    94: {"number": 94, "name": "os", "description": "KA9Q NOS compatible IP over IP tunneling"},
    95: {"number": 95, "name": "micp", "description": "Mobile Internetworking Control Protocol"},
    96: {"number": 96, "name": "scc-sp", "description": "Semaphore Communications Sec. Pro"},
    97: {
        "number": 97,
        "name": "etherip",
        "description": "Ethernet-within-IP Encapsulation, RFC 3378",
    },
    98: {"number": 98, "name": "encap", "description": "Encapsulation Header, RFC 1241"},
    99: {"number": 99, "name": "any", "description": "private encryption scheme"},
    100: {"number": 100, "name": "gmtp", "description": "GMTP"},
    101: {"number": 101, "name": "ifmp", "description": "Ipsilon Flow Management Protocol"},
    102: {"number": 102, "name": "pnni", "description": "PNNI over IP"},
    103: {"number": 103, "name": "pim", "description": "Protocol Independent Multicast"},
    104: {
        "number": 104,
        "name": "aris",
        "description": "IBM ARIS (Aggregate Route IP Switching) Protocol",
    },
    105: {
        "number": 105,
        "name": "scps",
        "description": "SCPS (Space Communications Protocol Standards), SCPS-TP",
    },
    106: {"number": 106, "name": "qnx", "description": "QNX"},
    107: {"number": 107, "name": "a/n", "description": "Active Networks"},
    108: {
        "number": 108,
        "name": "ipcomp",
        "description": "IP Payload Compression Protocol, RFC 3173",
    },
    109: {"number": 109, "name": "snp", "description": "Sitara Networks Protocol"},
    110: {"number": 110, "name": "compaq-peer", "description": "Compaq Peer Protocol"},
    111: {"number": 111, "name": "ipx-in-ip", "description": "IPX in IP"},
    112: {
        "number": 112,
        "name": "vrrp",
        "description": "Virtual Router Redundancy Protocol, "
        "Common Address Redundancy Protocol (not IANA assigned), 3768",
    },
    113: {"number": 113, "name": "pgm", "description": "PGM Reliable Transport Protocol, RFC 3208"},
    114: {"number": 114, "name": "any", "description": "0-hop protocol"},
    115: {
        "number": 115,
        "name": "l2tp",
        "description": "Layer Two Tunneling Protocol Version 3, RFC 3931",
    },
    116: {"number": 116, "name": "ddx", "description": "D-II Data Exchange (DDX)"},
    117: {"number": 117, "name": "iatp", "description": "Interactive Agent Transfer Protocol"},
    118: {"number": 118, "name": "stp", "description": "Schedule Transfer Protocol"},
    119: {"number": 119, "name": "srp", "description": "SpectraLink Radio Protocol"},
    120: {"number": 120, "name": "uti", "description": "Universal Transport Interface Protocol"},
    121: {"number": 121, "name": "smp", "description": "Simple Message Protocol"},
    122: {
        "number": 122,
        "name": "sm",
        "description": "Simple Multicast Protocol, draft-perlman-simple-multicast-03",
    },
    123: {"number": 123, "name": "ptp", "description": "Performance Transparency Protocol"},
    124: {
        "number": 124,
        "name": "is-is",
        "description": "Intermediate System to Intermediate System (IS-IS) Protocol over IPv4, "
        "RFC 1142 and RFC 1195",
    },
    125: {"number": 125, "name": "fire", "description": "Flexible Intra-AS Routing Environment"},
    126: {"number": 126, "name": "crtp", "description": "Combat Radio Transport Protocol"},
    127: {"number": 127, "name": "crudp", "description": "Combat Radio User Datagram"},
    128: {
        "number": 128,
        "name": "sscopmce",
        "description": "Service-Specific Connection-Oriented Protocol in a Multilink and "
        "Connectionless Environment, ITU-T Q.2111 (1999)",
    },
    129: {"number": 129, "name": "iplt", "description": ""},
    130: {"number": 130, "name": "sps", "description": "Secure Packet Shield"},
    131: {
        "number": 131,
        "name": "pipe",
        "description": "Private IP Encapsulation within IP, "
        "Expired I-D draft-petri-mobileip-pipe-00.txt",
    },
    132: {
        "number": 132,
        "name": "sctp",
        "description": "Stream Control Transmission Protocol, RFC 4960",
    },
    133: {"number": 133, "name": "fc", "description": "Fibre Channel"},
    134: {
        "number": 134,
        "name": "rsvp-e2e-ignore",
        "description": "Reservation Protocol (RSVP) End-to-End Ignore, RFC 3175",
    },
    135: {
        "number": 135,
        "name": "mobility",
        "description": "Header, Mobility Extension Header for IPv6, RFC 6275",
    },
    136: {
        "number": 136,
        "name": "udplite",
        "description": "Lightweight User Datagram Protocol, RFC 3828",
    },
    137: {
        "number": 137,
        "name": "mpls-in-ip",
        "description": "Multiprotocol Label Switching Encapsulated in IP, RFC 4023, RFC 5332",
    },
    138: {"number": 138, "name": "manet", "description": "MANET Protocols, RFC 5498"},
    139: {"number": 139, "name": "hip", "description": "Host Identity Protocol, RFC 5201"},
    140: {
        "number": 140,
        "name": "shim6",
        "description": "Site Multihoming by IPv6 Intermediation, RFC 5533",
    },
    141: {
        "number": 141,
        "name": "wesp",
        "description": "Wrapped Encapsulating Security Payload, RFC 5840",
    },
    142: {"number": 142, "name": "rohc", "description": "Robust Header Compression, RFC 5856"},
    143: {
        "number": 143,
        "name": "ethernet",
        "description": "IPv6 Segment Routing "
        "(TEMPORARY - registered 2020-01-31, expired 2021-01-31)",
    },
}
IP_NAMES_: DAny = {d["name"]: d for i, d in IP_NUMBERS.items()}
IP_ALIASES = {
    "ipinip": {"number": 4, "name": "ipinip", "description": "IP in IP (encapsulation), RFC 2003"},
    "iptm": {"number": 84, "name": "iptm", "description": "Internet Protocol Traffic Manager"},
}
IP_NAMES = {**IP_NAMES_, **IP_ALIASES}

MIN_NUMBER = 0
MAX_NUMBER = 255
ALL_NUMBERS_L = list(range(MIN_NUMBER, MAX_NUMBER + 1))
ALL_NUMBERS_S = f"{MIN_NUMBER}-{MAX_NUMBER}"


# noinspection PyIncorrectDocstring
def iip(items: Any = "", **kwargs) -> LInt:
    """Sorting IP protocol numbers and removing duplicates.

    :param items: Range of IP protocol numbers, can be unsorted and with duplicates,
        "ip" - Return all IP protocol numbers: [0, 1, ..., 255].
    :type items: str or List[int] or List[str]

    :param verbose: True - all protocol numbers in verbose mode: [0, 1, ..., 255],
        False - all protocol numbers in brief mode: [-1] to save RAM (default).
    :type verbose: bool

    :param strict: True - Raises NetportsValueError, if the protocol is unknown (default),
        False - Skips unknown protocols.

    :param all: True - Return all IP protocol numbers: [0, 1, ..., 255].
    :type all: bool

    :return: List[int] of unique sorted IP protocol numbers.
    :rtype: List[int]

    :raises NetportsValueError: If IP protocol numbers are outside valid range 0...255.

    :example: Converts mix of ip protocol names and numbers to the numbers.
        iip("icmp,tcp,7,255") -> [1, 6, 7, 255]

    :example: Converts ip to full range of ip protocol numbers.
        iip("ip") -> [0, 1, ... 254, 255]
    """
    if h.is_all(**kwargs):
        if h.is_brief(**kwargs):
            return [BRIEF_ALL_I]
        return ALL_NUMBERS_L.copy()
    if h.is_brief(**kwargs):
        if h.is_brief_in_items(items):
            return [BRIEF_ALL_I]

    items_ = [s.lower() for s in h.split(items)]

    # 0-255
    if ALL_NUMBERS_S in items_:
        if h.is_brief(**kwargs):
            return [BRIEF_ALL_I]
        return ALL_NUMBERS_L.copy()

    # names, numbers
    pairs, invalid = ip_pairs(items=items_, **kwargs)
    if h.is_strict(**kwargs):
        if invalid:
            raise NetportsValueError(f"{invalid=}")
    if not pairs:
        return []
    numbers, _ = [list(t) for t in zip(*pairs)]

    if h.is_brief(**kwargs):
        if numbers == ALL_NUMBERS_L:
            return [BRIEF_ALL_I]
    return numbers


# noinspection PyIncorrectDocstring
def sip(items: Any = "", **kwargs) -> str:
    """Soring string IP protocol numbers and removing duplicates.

    :param items: Range of IP protocol numbers, can be unsorted and with duplicates,
        "ip" - mean all numbers in range 0...255.
    :type items: str or List[int] or List[str]

    :param verbose: True - all protocol numbers in verbose mode: [0, 1, ..., 255],
        False - all protocol numbers in brief mode: [-1], to save RAM (default).
    :type verbose: bool

    :param strict: True - Raises NetportsValueError, if the protocol is unknown (default),
        False - Skips unknown protocols.
    :type strict: bool

    :param all: True - Return all IP protocol numbers: "0-255".
    :type all: bool

    :return: str of unique sorted IP protocol numbers.
    :rtype: str

    :raises NetportsValueError: If IP protocol numbers are outside valid range 0...255.

    :example:
        sip(["icmp", "tcp", "7", 255]) -> "1,6-7,255"
    """
    if h.is_all(**kwargs):
        return ALL_NUMBERS_S
    if h.is_brief(**kwargs):
        if h.is_brief_in_items(items):
            return ALL_NUMBERS_S

    numbers = iip(items, **kwargs)
    if h.is_brief_in_items(numbers):
        return ALL_NUMBERS_S

    return snumbers(numbers)


# noinspection PyIncorrectDocstring
def ip_pairs(items: Any, **kwargs) -> Tuple[LTIntStr, LStr]:
    """Split items to IP protocol Number, Name and undefined-invalid protocols.

    :param items: Range of IP protocols, can be unsorted and with duplicates.
    :type items: str or List[int] or List[str]

    :param verbose: True - all protocol numbers in verbose mode: [0, 1, ..., 255],
        False - all protocol numbers in brief mode: [-1], to save RAM (default).
    :type verbose: bool

    :return: *List[Tuple[int, str]]* Pairs of IP protocol number and name,
        List[str] Undefined protocol names and invalid numbers.
    :rtype: Tuple[List[Tuple[int, str]], List[str]]

    :example:
        ip_pairs("tcp,1,typo,256") -> [(1, "icmp"), (6, "tcp")], ["256", "typo"]
        ip_pairs(["tcp", 1, "typo", 256]) -> [(1, "icmp"), (6, "tcp")], ["256", "typo"]
    """
    items_: LStr = [s.lower() for s in h.split(items)]
    if "ip" in items_:
        if h.is_brief(**kwargs):
            return [(BRIEF_ALL_I, "ip")], []
        return ALL_PAIRS.copy(), []
    if h.is_brief(**kwargs):
        if h.is_brief_in_items(items):
            return [(BRIEF_ALL_I, "ip")], []

    numbers, names = _split_numbers_names(items_)
    names, undefined = _split_names_undefined(names)
    numbers, invalid = _split_numbers_invalid(numbers)
    numbers.update({d["number"] for s, d in IP_NAMES.items() if s in names})
    invalid.update(undefined)

    pairs: LTIntStr = []
    for name in sorted(names):
        number = IP_NAMES[name]["number"]
        if number not in numbers:
            continue
        numbers.remove(number)
        pairs.append((number, name))
    for number in sorted(numbers):
        name = IP_NUMBERS.get(number, {}).get("name") or ""
        pairs.append((number, name))
    pairs.sort(key=itemgetter(0))

    if h.is_brief(**kwargs):
        if pairs == ALL_PAIRS:
            pairs = [(BRIEF_ALL_I, "ip")]

    return pairs, sorted(invalid)


# ============================= helpers ==============================


def all_pairs():
    """Return all pairs of IP protocol (number, name)."""
    paris = []
    for number in ALL_NUMBERS_L:
        name = IP_NUMBERS.get(number, {}).get("name") or ""
        paris.append((number, name))
    return paris


def _split_numbers_names(items: LStr) -> Tuple[SInt, SStr]:
    """Split `items` to the numbers and names.

    :param items: Combo of numbers and names.
    :return: numbers, names.
    """
    names: SStr = set()
    numbers: SInt = set()
    for item in items:
        try:
            range_o = Range(item)
            numbers.update(range_o.numbers())
        except NetportsValueError:
            names.add(item)
    return numbers, names


def _split_names_undefined(items: SStr) -> Tuple[SStr, SStr]:
    """Split `items` to the defined names and undefined names.

    :param items: Combo of defined names and undefined names.
    :return: defined names, undefined names.
    """
    names: SStr = set()
    undefined: SStr = set()
    for item in items:
        if item in IP_NAMES_:
            names.add(item)
            continue
        if item in IP_ALIASES:
            names.add(item)
            continue
        undefined.add(item)
    return names, undefined


def _split_numbers_invalid(items: SInt) -> Tuple[SInt, SStr]:
    """Split `items` to the valid protocol numbers and invalid numbers.

    :param items: Combo of valid and invalid numbers.
    :return: valid numbers, invalid numbers.
    """
    numbers: SInt = set()
    invalid: SInt = set()
    for item in items:
        if item in ALL_NUMBERS_L:
            numbers.add(item)
        else:
            invalid.add(item)
    invalid_: SStr = {str(i) for i in invalid}
    return numbers, invalid_


ALL_PAIRS = all_pairs()
