"""
bird.py: defines routing services provided by the BIRD Internet Routing Daemon.
"""
from typing import Optional, Tuple

from core.nodes.base import CoreNode
from core.services.coreservices import CoreService


class Bird(CoreService):
    """
    Bird router support
    """

    name: str = "bird"
    group: str = "BIRD"
    executables: Tuple[str, ...] = ("bird",)
    dirs: Tuple[str, ...] = ("/etc/bird",)
    configs: Tuple[str, ...] = ("/etc/bird/bird.conf",)
    startup: Tuple[str, ...] = ("bird -c %s" % (configs[0]),)
    shutdown: Tuple[str, ...] = ("killall bird",)
    validate: Tuple[str, ...] = ("pidof bird",)

    @classmethod
    def generate_config(cls, node: CoreNode, filename: str) -> str:
        """
        Return the bird.conf file contents.
        """
        if filename == cls.configs[0]:
            return cls.generate_bird_config(node)
        else:
            raise ValueError

    @staticmethod
    def router_id(node: CoreNode) -> str:
        """
        Helper to return the first IPv4 address of a node as its router ID.
        """
        for iface in node.get_ifaces(control=False):
            ip4 = iface.get_ip4()
            if ip4:
                return str(ip4.ip)
        return "0.0.0.0"

    @classmethod
    def generate_bird_config(cls, node: CoreNode) -> str:
        """
        Returns configuration file text. Other services that depend on bird
        will have hooks that are invoked here.
        """
        cfg = """\
/* Main configuration file for BIRD. This is ony a template,
 * you will *need* to customize it according to your needs
 * Beware that only double quotes \'"\' are valid. No singles. */


log "/var/log/%s.log" all;
#debug protocols all;
#debug commands 2;

router id  %s;       # Mandatory for IPv6, may be automatic for IPv4

protocol kernel {
    persist;                # Don\'t remove routes on BIRD shutdown
    scan time 200;          # Scan kernel routing table every 200 seconds
    export all;
    import all;
}

protocol device {
    scan time 10;           # Scan interfaces every 10 seconds
}

""" % (
            cls.name,
            cls.router_id(node),
        )

        # generate protocol specific configurations
        for s in node.services:
            if cls.name not in s.dependencies:
                continue
            if not (isinstance(s, BirdService) or issubclass(s, BirdService)):
                continue
            cfg += s.generate_bird_config(node)
        return cfg


class BirdService(CoreService):
    """
    Parent class for Bird services. Defines properties and methods
    common to Bird's routing daemons.
    """

    name: Optional[str] = None
    group: str = "BIRD"
    executables: Tuple[str, ...] = ("bird",)
    dependencies: Tuple[str, ...] = ("bird",)
    meta: str = "The config file for this service can be found in the bird service."

    @classmethod
    def generate_bird_config(cls, node: CoreNode) -> str:
        return ""

    @classmethod
    def generate_bird_iface_config(cls, node: CoreNode) -> str:
        """
        Use only bare interfaces descriptions in generated protocol
        configurations. This has the slight advantage of being the same
        everywhere.
        """
        cfg = ""
        for iface in node.get_ifaces(control=False):
            cfg += '        interface "%s";\n' % iface.name
        return cfg


class BirdBgp(BirdService):
    """
    BGP BIRD Service (configuration generation)
    """

    name: str = "BIRD_BGP"
    custom_needed: bool = True

    @classmethod
    def generate_bird_config(cls, node: CoreNode) -> str:
        return """
/* This is a sample config that should be customized with appropriate AS numbers
 * and peers; add one section like this for each neighbor */

protocol bgp {
    local as 65000;                      # Customize your AS number
    neighbor 198.51.100.130 as 64496;    # Customize neighbor AS number && IP
    export filter {                      # We use non-trivial export rules
        # This is an example. You should advertise only *your routes*
        if (source = RTS_DEVICE) || (source = RTS_OSPF) then {
#           bgp_community.add((65000,64501)); # Assign our community
            accept;
        }
        reject;
    };
    import all;
}

"""


class BirdOspf(BirdService):
    """
    OSPF BIRD Service (configuration generation)
    """

    name: str = "BIRD_OSPFv2"

    @classmethod
    def generate_bird_config(cls, node: CoreNode) -> str:
        cfg = "protocol ospf {\n"
        cfg += "    export filter {\n"
        cfg += "        if source = RTS_BGP then {\n"
        cfg += "            ospf_metric1 = 100;\n"
        cfg += "            accept;\n"
        cfg += "        }\n"
        cfg += "        accept;\n"
        cfg += "    };\n"
        cfg += "    area 0.0.0.0 {\n"
        cfg += cls.generate_bird_iface_config(node)
        cfg += "    };\n"
        cfg += "}\n\n"
        return cfg


class BirdRadv(BirdService):
    """
    RADV BIRD Service (configuration generation)
    """

    name: str = "BIRD_RADV"

    @classmethod
    def generate_bird_config(cls, node: CoreNode) -> str:
        cfg = "/* This is a sample config that must be customized */\n"
        cfg += "protocol radv {\n"
        cfg += "    # auto configuration on all interfaces\n"
        cfg += cls.generate_bird_iface_config(node)
        cfg += "    # Advertise DNS\n"
        cfg += "    rdnss {\n"
        cfg += "#        lifetime mult 10;\n"
        cfg += "#        lifetime mult 10;\n"
        cfg += "#        ns 2001:0DB8:1234::11;\n"
        cfg += "#        ns 2001:0DB8:1234::11;\n"
        cfg += "#        ns 2001:0DB8:1234::12;\n"
        cfg += "#        ns 2001:0DB8:1234::12;\n"
        cfg += "    };\n"
        cfg += "}\n\n"
        return cfg


class BirdRip(BirdService):
    """
    RIP BIRD Service (configuration generation)
    """

    name: str = "BIRD_RIP"

    @classmethod
    def generate_bird_config(cls, node: CoreNode) -> str:
        cfg = "protocol rip {\n"
        cfg += "    period 10;\n"
        cfg += "    garbage time 60;\n"
        cfg += cls.generate_bird_iface_config(node)
        cfg += "    honor neighbor;\n"
        cfg += "    authentication none;\n"
        cfg += "    import all;\n"
        cfg += "    export all;\n"
        cfg += "}\n\n"
        return cfg


class BirdStatic(BirdService):
    """
    Static Bird Service (configuration generation)
    """

    name: str = "BIRD_static"
    custom_needed: bool = True

    @classmethod
    def generate_bird_config(cls, node: CoreNode) -> str:
        cfg = "/* This is a sample config that must be customized */\n"
        cfg += "protocol static {\n"
        cfg += "#    route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n"
        cfg += "#    route 203.0.113.0/24 reject;        # Sink route\n"
        cfg += '#    route 10.2.0.0/24 via "arc0";       # Secondary network\n'
        cfg += "}\n\n"
        return cfg