"""
xorp.py: defines routing services provided by the XORP routing suite.
"""

from typing import Optional, Tuple

import netaddr

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


class XorpRtrmgr(CoreService):
    """
    XORP router manager service builds a config.boot file based on other
    enabled XORP services, and launches necessary daemons upon startup.
    """

    name: str = "xorp_rtrmgr"
    group: str = "XORP"
    executables: Tuple[str, ...] = ("xorp_rtrmgr",)
    dirs: Tuple[str, ...] = ("/etc/xorp",)
    configs: Tuple[str, ...] = ("/etc/xorp/config.boot",)
    startup: Tuple[str, ...] = (
        "xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid"
        % (configs[0], name, name),
    )
    shutdown: Tuple[str, ...] = ("killall xorp_rtrmgr",)
    validate: Tuple[str, ...] = ("pidof xorp_rtrmgr",)

    @classmethod
    def generate_config(cls, node: CoreNode, filename: str) -> str:
        """
        Returns config.boot configuration file text. Other services that
        depend on this will have generatexorpconfig() hooks that are
        invoked here. Filename currently ignored.
        """
        cfg = "interfaces {\n"
        for iface in node.get_ifaces():
            cfg += "    interface %s {\n" % iface.name
            cfg += "\tvif %s {\n" % iface.name
            cfg += "".join(map(cls.addrstr, iface.ips()))
            cfg += cls.lladdrstr(iface)
            cfg += "\t}\n"
            cfg += "    }\n"
        cfg += "}\n\n"

        for s in node.services:
            if cls.name not in s.dependencies:
                continue
            if not (isinstance(s, XorpService) or issubclass(s, XorpService)):
                continue
            cfg += s.generate_xorp_config(node)
        return cfg

    @staticmethod
    def addrstr(ip: netaddr.IPNetwork) -> str:
        """
        helper for mapping IP addresses to XORP config statements
        """
        cfg = "\t    address %s {\n" % ip.ip
        cfg += "\t\tprefix-length: %s\n" % ip.prefixlen
        cfg += "\t    }\n"
        return cfg

    @staticmethod
    def lladdrstr(iface: CoreInterface) -> str:
        """
        helper for adding link-local address entries (required by OSPFv3)
        """
        cfg = "\t    address %s {\n" % iface.mac.eui64()
        cfg += "\t\tprefix-length: 64\n"
        cfg += "\t    }\n"
        return cfg


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

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

    @staticmethod
    def fea(forwarding: str) -> str:
        """
        Helper to add a forwarding engine entry to the config file.
        """
        cfg = "fea {\n"
        cfg += "    %s {\n" % forwarding
        cfg += "\tdisable:false\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg

    @staticmethod
    def mfea(forwarding, node: CoreNode) -> str:
        """
        Helper to add a multicast forwarding engine entry to the config file.
        """
        names = []
        for iface in node.get_ifaces(control=False):
            names.append(iface.name)
        names.append("register_vif")
        cfg = "plumbing {\n"
        cfg += "    %s {\n" % forwarding
        for name in names:
            cfg += "\tinterface %s {\n" % name
            cfg += "\t    vif %s {\n" % name
            cfg += "\t\tdisable: false\n"
            cfg += "\t    }\n"
            cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg

    @staticmethod
    def policyexportconnected() -> str:
        """
        Helper to add a policy statement for exporting connected routes.
        """
        cfg = "policy {\n"
        cfg += "    policy-statement export-connected {\n"
        cfg += "\tterm 100 {\n"
        cfg += "\t    from {\n"
        cfg += '\t\tprotocol: "connected"\n'
        cfg += "\t    }\n"
        cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg

    @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_config(cls, node: CoreNode, filename: str) -> str:
        return ""

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


class XorpOspfv2(XorpService):
    """
    The OSPFv2 service provides IPv4 routing for wired networks. It does
    not build its own configuration file but has hooks for adding to the
    unified XORP configuration file.
    """

    name: str = "XORP_OSPFv2"

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = cls.fea("unicast-forwarding4")
        rtrid = cls.router_id(node)
        cfg += "\nprotocols {\n"
        cfg += "    ospf4 {\n"
        cfg += "\trouter-id: %s\n" % rtrid
        cfg += "\tarea 0.0.0.0 {\n"
        for iface in node.get_ifaces(control=False):
            cfg += "\t    interface %s {\n" % iface.name
            cfg += "\t\tvif %s {\n" % iface.name
            for ip4 in iface.ip4s:
                cfg += "\t\t    address %s {\n" % ip4.ip
                cfg += "\t\t    }\n"
            cfg += "\t\t}\n"
            cfg += "\t    }\n"
        cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg


class XorpOspfv3(XorpService):
    """
    The OSPFv3 service provides IPv6 routing. It does
    not build its own configuration file but has hooks for adding to the
    unified XORP configuration file.
    """

    name: str = "XORP_OSPFv3"

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = cls.fea("unicast-forwarding6")
        rtrid = cls.router_id(node)
        cfg += "\nprotocols {\n"
        cfg += "    ospf6 0 { /* Instance ID 0 */\n"
        cfg += "\trouter-id: %s\n" % rtrid
        cfg += "\tarea 0.0.0.0 {\n"
        for iface in node.get_ifaces(control=False):
            cfg += "\t    interface %s {\n" % iface.name
            cfg += "\t\tvif %s {\n" % iface.name
            cfg += "\t\t}\n"
            cfg += "\t    }\n"
        cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg


class XorpBgp(XorpService):
    """
    IPv4 inter-domain routing. AS numbers and peers must be customized.
    """

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

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = "/* This is a sample config that should be customized with\n"
        cfg += " appropriate AS numbers and peers */\n"
        cfg += cls.fea("unicast-forwarding4")
        cfg += cls.policyexportconnected()
        rtrid = cls.router_id(node)
        cfg += "\nprotocols {\n"
        cfg += "    bgp {\n"
        cfg += "\tbgp-id: %s\n" % rtrid
        cfg += "\tlocal-as: 65001 /* change this */\n"
        cfg += '\texport: "export-connected"\n'
        cfg += "\tpeer 10.0.1.1 { /* change this */\n"
        cfg += "\t    local-ip: 10.0.1.1\n"
        cfg += "\t    as: 65002\n"
        cfg += "\t    next-hop: 10.0.0.2\n"
        cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg


class XorpRip(XorpService):
    """
    RIP IPv4 unicast routing.
    """

    name: str = "XORP_RIP"

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = cls.fea("unicast-forwarding4")
        cfg += cls.policyexportconnected()
        cfg += "\nprotocols {\n"
        cfg += "    rip {\n"
        cfg += '\texport: "export-connected"\n'
        for iface in node.get_ifaces(control=False):
            cfg += "\tinterface %s {\n" % iface.name
            cfg += "\t    vif %s {\n" % iface.name
            for ip4 in iface.ip4s:
                cfg += "\t\taddress %s {\n" % ip4.ip
                cfg += "\t\t    disable: false\n"
                cfg += "\t\t}\n"
            cfg += "\t    }\n"
            cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg


class XorpRipng(XorpService):
    """
    RIP NG IPv6 unicast routing.
    """

    name: str = "XORP_RIPNG"

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = cls.fea("unicast-forwarding6")
        cfg += cls.policyexportconnected()
        cfg += "\nprotocols {\n"
        cfg += "    ripng {\n"
        cfg += '\texport: "export-connected"\n'
        for iface in node.get_ifaces(control=False):
            cfg += "\tinterface %s {\n" % iface.name
            cfg += "\t    vif %s {\n" % iface.name
            cfg += "\t\taddress %s {\n" % iface.mac.eui64()
            cfg += "\t\t    disable: false\n"
            cfg += "\t\t}\n"
            cfg += "\t    }\n"
            cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg


class XorpPimSm4(XorpService):
    """
    PIM Sparse Mode IPv4 multicast routing.
    """

    name: str = "XORP_PIMSM4"

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = cls.mfea("mfea4", node)
        cfg += "\nprotocols {\n"
        cfg += "    igmp {\n"
        names = []
        for iface in node.get_ifaces(control=False):
            names.append(iface.name)
            cfg += "\tinterface %s {\n" % iface.name
            cfg += "\t    vif %s {\n" % iface.name
            cfg += "\t\tdisable: false\n"
            cfg += "\t    }\n"
            cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        cfg += "\nprotocols {\n"
        cfg += "    pimsm4 {\n"

        names.append("register_vif")
        for name in names:
            cfg += "\tinterface %s {\n" % name
            cfg += "\t    vif %s {\n" % name
            cfg += "\t\tdr-priority: 1\n"
            cfg += "\t    }\n"
            cfg += "\t}\n"
        cfg += "\tbootstrap {\n"
        cfg += "\t    cand-bsr {\n"
        cfg += "\t\tscope-zone 224.0.0.0/4 {\n"
        cfg += '\t\t    cand-bsr-by-vif-name: "%s"\n' % names[0]
        cfg += "\t\t}\n"
        cfg += "\t    }\n"
        cfg += "\t    cand-rp {\n"
        cfg += "\t\tgroup-prefix 224.0.0.0/4 {\n"
        cfg += '\t\t    cand-rp-by-vif-name: "%s"\n' % names[0]
        cfg += "\t\t}\n"
        cfg += "\t    }\n"
        cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        cfg += "\nprotocols {\n"
        cfg += "    fib2mrib {\n"
        cfg += "\tdisable: false\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg


class XorpPimSm6(XorpService):
    """
    PIM Sparse Mode IPv6 multicast routing.
    """

    name: str = "XORP_PIMSM6"

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = cls.mfea("mfea6", node)
        cfg += "\nprotocols {\n"
        cfg += "    mld {\n"
        names = []
        for iface in node.get_ifaces(control=False):
            names.append(iface.name)
            cfg += "\tinterface %s {\n" % iface.name
            cfg += "\t    vif %s {\n" % iface.name
            cfg += "\t\tdisable: false\n"
            cfg += "\t    }\n"
            cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        cfg += "\nprotocols {\n"
        cfg += "    pimsm6 {\n"

        names.append("register_vif")
        for name in names:
            cfg += "\tinterface %s {\n" % name
            cfg += "\t    vif %s {\n" % name
            cfg += "\t\tdr-priority: 1\n"
            cfg += "\t    }\n"
            cfg += "\t}\n"
        cfg += "\tbootstrap {\n"
        cfg += "\t    cand-bsr {\n"
        cfg += "\t\tscope-zone ff00::/8 {\n"
        cfg += '\t\t    cand-bsr-by-vif-name: "%s"\n' % names[0]
        cfg += "\t\t}\n"
        cfg += "\t    }\n"
        cfg += "\t    cand-rp {\n"
        cfg += "\t\tgroup-prefix ff00::/8 {\n"
        cfg += '\t\t    cand-rp-by-vif-name: "%s"\n' % names[0]
        cfg += "\t\t}\n"
        cfg += "\t    }\n"
        cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        cfg += "\nprotocols {\n"
        cfg += "    fib2mrib {\n"
        cfg += "\tdisable: false\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg


class XorpOlsr(XorpService):
    """
    OLSR IPv4 unicast MANET routing.
    """

    name: str = "XORP_OLSR"

    @classmethod
    def generate_xorp_config(cls, node: CoreNode) -> str:
        cfg = cls.fea("unicast-forwarding4")
        rtrid = cls.router_id(node)
        cfg += "\nprotocols {\n"
        cfg += "    olsr4 {\n"
        cfg += "\tmain-address: %s\n" % rtrid
        for iface in node.get_ifaces(control=False):
            cfg += "\tinterface %s {\n" % iface.name
            cfg += "\t    vif %s {\n" % iface.name
            for ip4 in iface.ip4s:
                cfg += "\t\taddress %s {\n" % ip4.ip
                cfg += "\t\t}\n"
            cfg += "\t    }\n"
        cfg += "\t}\n"
        cfg += "    }\n"
        cfg += "}\n"
        return cfg