"""Validate VRF configuration variables."""

# Standard Library
from typing import List, Optional
from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network

# Third Party
from pydantic import (
    Field,
    FilePath,
    StrictStr,
    StrictBool,
    conint,
    constr,
    validator,
    root_validator,
)

# Project
from hyperglass.configuration.models._utils import HyperglassModel, HyperglassModelExtra


class AccessList4(HyperglassModel):
    """Validation model for IPv4 access-lists."""

    network: IPv4Network = Field(
        "0.0.0.0/0",
        title="Network",
        description="IPv4 Network/Prefix that should be permitted or denied. ",
    )
    action: constr(regex=r"permit|deny") = Field(
        "permit",
        title="Action",
        description="Permit or deny any networks contained within the prefix.",
    )
    ge: conint(ge=0, le=32) = Field(
        0,
        title="Greater Than or Equal To",
        description="Similar to `ge` in a Cisco prefix-list, the `ge` field defines the **bottom** threshold for prefix size. For example, a value of `24` would result in a query for `192.0.2.0/23` being denied, but a query for `192.0.2.0/32` would be permitted. If this field is set to a value smaller than the `network` field's prefix length, this field's value will be overwritten to the prefix length of the prefix in the `network` field.",
    )
    le: conint(ge=0, le=32) = Field(
        32,
        title="Less Than or Equal To",
        description="Similar to `le` in a Cisco prefix-list, the `le` field defines the **top** threshold for prefix size. For example, a value of `24` would result in a query for `192.0.2.0/23` being permitted, but a query for `192.0.2.0/32` would be denied.",
    )

    @validator("ge")
    def validate_model(cls, value, values):
        """Ensure ge is at least the size of the input prefix.

        Arguments:
            value {int} -- Initial ge value
            values {dict} -- Other post-validation fields

        Returns:
            {int} -- Validated ge value
        """
        net_len = values["network"].prefixlen
        if net_len > value:
            value = net_len
        return value


class AccessList6(HyperglassModel):
    """Validation model for IPv6 access-lists."""

    network: IPv6Network = Field(
        "::/0",
        title="Network",
        description="IPv6 Network/Prefix that should be permitted or denied. ",
    )
    action: constr(regex=r"permit|deny") = Field(
        "permit",
        title="Action",
        description="Permit or deny any networks contained within the prefix.",
    )
    ge: conint(ge=0, le=128) = Field(
        0,
        title="Greater Than or Equal To",
        description="Similar to `ge` in a Cisco prefix-list, the `ge` field defines the **bottom** threshold for prefix size. For example, a value of `64` would result in a query for `2001:db8::/48` being denied, but a query for `2001:db8::1/128` would be permitted. If this field is set to a value smaller than the `network` field's prefix length, this field's value will be overwritten to the prefix length of the prefix in the `network` field.",
    )
    le: conint(ge=0, le=128) = Field(
        128,
        title="Less Than or Equal To",
        description="Similar to `le` in a Cisco prefix-list, the `le` field defines the **top** threshold for prefix size. For example, a value of `64` would result in a query for `2001:db8::/48` being permitted, but a query for `2001:db8::1/128` would be denied.",
    )

    @validator("ge")
    def validate_model(cls, value, values):
        """Ensure ge is at least the size of the input prefix.

        Arguments:
            value {int} -- Initial ge value
            values {dict} -- Other post-validation fields

        Returns:
            {int} -- Validated ge value
        """
        net_len = values["network"].prefixlen
        if net_len > value:
            value = net_len
        return value


class InfoConfigParams(HyperglassModelExtra):
    """Validation model for per-help params."""

    title: Optional[StrictStr]

    class Config:
        """Pydantic model configuration."""

        title = "Help Parameters"
        description = "Set dynamic or reusable values which may be used in the help/information content. Params my be access by using Python string formatting syntax, e.g. `{param_name}`. Any arbitrary values may be added."


class InfoConfig(HyperglassModel):
    """Validation model for help configuration."""

    enable: StrictBool = True
    file: Optional[FilePath]
    params: InfoConfigParams = InfoConfigParams()

    class Config:
        """Pydantic model configuration."""

        fields = {
            "enable": {
                "title": "Enable",
                "description": "Enable or disable the display of help/information content for this query type.",
            },
            "file": {
                "title": "File Name",
                "description": "Path to a valid text or Markdown file containing custom content.",
            },
        }


class Info(HyperglassModel):
    """Validation model for per-VRF, per-Command help."""

    bgp_aspath: InfoConfig = InfoConfig()
    bgp_community: InfoConfig = InfoConfig()
    bgp_route: InfoConfig = InfoConfig()
    ping: InfoConfig = InfoConfig()
    traceroute: InfoConfig = InfoConfig()

    class Config:
        """Pydantic model configuration."""

        title = "VRF Information"
        description = "Per-VRF help & information content."
        fields = {
            "bgp_aspath": {
                "title": "BGP AS Path",
                "description": "Show information about bgp_aspath queries when selected.",
            },
            "bgp_community": {
                "title": "BGP Community",
                "description": "Show information about bgp_community queries when selected.",
            },
            "bgp_route": {
                "title": "BGP Route",
                "description": "Show information about bgp_route queries when selected.",
            },
            "ping": {
                "title": "Ping",
                "description": "Show information about ping queries when selected.",
            },
            "traceroute": {
                "title": "Traceroute",
                "description": "Show information about traceroute queries when selected.",
            },
        }


class DeviceVrf4(HyperglassModelExtra):
    """Validation model for IPv4 AFI definitions."""

    source_address: IPv4Address
    access_list: List[AccessList4] = [AccessList4()]
    force_cidr: StrictBool = True


class DeviceVrf6(HyperglassModelExtra):
    """Validation model for IPv6 AFI definitions."""

    source_address: IPv6Address
    access_list: List[AccessList6] = [AccessList6()]
    force_cidr: StrictBool = True


class Vrf(HyperglassModel):
    """Validation model for per VRF/afi config in devices.yaml."""

    name: StrictStr
    display_name: StrictStr
    info: Info = Info()
    ipv4: Optional[DeviceVrf4]
    ipv6: Optional[DeviceVrf6]

    @root_validator
    def set_dynamic(cls, values):
        """Set dynamic attributes before VRF initialization.

        Arguments:
            values {dict} -- Post-validation VRF attributes

        Returns:
            {dict} -- VRF with new attributes set
        """
        if values["name"] == "default":
            protocol4 = "ipv4_default"
            protocol6 = "ipv6_default"

        else:
            protocol4 = "ipv4_vpn"
            protocol6 = "ipv6_vpn"

        if values.get("ipv4") is not None:
            values["ipv4"].protocol = protocol4
            values["ipv4"].version = 4

        if values.get("ipv6") is not None:
            values["ipv6"].protocol = protocol6
            values["ipv6"].version = 6

        if values.get("name") == "default" and values.get("display_name") is None:
            values["display_name"] = "Global"

        return values

    def __getitem__(self, i):
        """Access the VRF's AFI by IP protocol number.

        Arguments:
            i {int} -- IP Protocol number (4|6)

        Raises:
            AttributeError: Raised if passed number is not 4 or 6.

        Returns:
            {object} -- AFI object
        """
        if i not in (4, 6):
            raise AttributeError(f"Must be 4 or 6, got '{i}")

        return getattr(self, f"ipv{i}")

    def __hash__(self):
        """Make VRF object hashable so the object can be deduplicated with set().

        Returns:
            {int} -- Hash of VRF name
        """
        return hash((self.name,))

    def __eq__(self, other):
        """Make VRF object comparable so the object can be deduplicated with set().

        Arguments:
            other {object} -- Object to compare

        Returns:
            {bool} -- True if comparison attributes are the same value
        """
        result = False
        if isinstance(other, HyperglassModel):
            result = self.name == other.name
        return result

    class Config:
        """Pydantic model configuration."""

        title = "VRF"
        description = "Per-VRF configuration."
        fields = {
            "name": {
                "title": "Name",
                "description": "VRF name as configured on the router/device.",
            },
            "display_name": {
                "title": "Display Name",
                "description": "Display name of VRF for use in the hyperglass UI. If none is specified, hyperglass will attempt to generate one.",
            },
        }
