import ipaddress
import random
from dataclasses import dataclass
from urllib.parse import urlparse

from fugue.lib.interface import Interface
from fugue.lib.peer import Peer
from fugue.lib.subprocess import wg
from fugue.lib.tunnel import Tunnel

"""
What's a Hub and Spoke topology[1]
Configure Wireguard for Hub and Spoke topology[2]

[1] https://www.procustodibus.com/blog/2020/10/wireguard-topologies/#hub-and-spoke
[2] https://www.procustodibus.com/blog/2020/11/wireguard-hub-and-spoke-config/
"""


@dataclass
class Hub:
    name: str
    tunnel: Tunnel
    as_peer: Peer
    # TODO: add a spokes attributes? Might introduce a cyclic dependency
    # spokes: List[Spoke]

    @staticmethod
    def autogen(
        name: str,
        listen_port: int,
        endpoint: str,
    ) -> "Hub":
        """
        Auto-generates a Hub and accompanying objects (i.e. tunnel, as_peer,
        interface) from a name, listening port and publicly reachable endpoint.
        """
        # TODO: an interactive mode?
        # TODO: checks on pre-existence of such hub name
        # TODO: slugify things
        priv_key: str = wg.gen_priv_key()
        gen_network_str: str = (
            f"10.{random.randint(0, 255)}.{random.randint(0, 255)}.0/24"
        )
        gen_network: ipaddress.IPv4Network = ipaddress.IPv4Network(
            gen_network_str
        )
        # NOTE: Pro Custodibus hints at an address with a /32 mask, when the
        # unofficial docs say that for "bounce servers", we can set an address
        # with the relevant VPN CIDR block mask. See:
        # * https://www.procustodibus.com/blog/2020/11/wireguard-hub-and-spoke-config/#configure-routing-on-host-c
        # * https://github.com/pirate/wireguard-docs#address
        interface: Interface = Interface(
            name,
            priv_key,
            ipaddress.IPv4Network(f"{gen_network.network_address}/24"),
            listen_port=listen_port,
            dns=[gen_network.network_address],
            table=None,
            mtu=None,
            pre_ups=["sysctl -w net.ipv4.ip_forward=1"],
            post_ups=None,
            pre_downs=None,
            post_downs=None,
        )
        # NOTE: if the Hub is behind a NAT, it looks like it'd be relevant to
        # send a persistent keepalive. I'm not one hunder percent on this
        # though - on Pro Custodibus, the diagram clearly shows the hub as
        # being behind a NAT. See:
        # * https://www.procustodibus.com/blog/2020/11/wireguard-hub-and-spoke-config/#configure-routing-on-host-c
        # * https://github.com/pirate/wireguard-docs#PersistentKeepalive
        as_peer: Peer = Peer(
            name,
            wg.gen_pub_key(priv_key),
            [gen_network],
            endpoint=urlparse(endpoint),
            persistent_keepalive=25,
        )
        tunnel: Tunnel = Tunnel(name, interface, [])
        return Hub(name, tunnel, as_peer)

    def start(self):
        self.tunnel.start()

    def stop(self):
        self.tunnel.stop()

    def enable(self):
        self.tunnel.enable()

    def disable(self):
        self.tunnel.disable()

    def forward(self, external_port=None):
        """
        Uses uPnP to automatically create/renew a port-forward if needed.

        WIP.
        """
        pass
