import ipaddress
from dataclasses import dataclass
from typing import List

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


@dataclass
class Spoke:
    name: str
    # TODO: make it optional for a spoke to have a Tunnel
    tunnel: Tunnel
    as_peer: Peer
    hub: Hub

    @staticmethod
    def autogen(name: str, hub: Hub) -> "Spoke":
        # new spoke address, and allowed_ip is max() + 1
        hub_peers_allowed_ips: List[List[ipaddress.IPv4Network]] = [
            peer.allowed_ips for peer in hub.tunnel.peers
        ]
        hub_peers_network_addresses: List[ipaddress.IPv4Address] = [
            allowed_ip.network_address
            for allowed_ips in hub_peers_allowed_ips
            for allowed_ip in allowed_ips
        ]
        address: ipaddress.IPv4Network
        if hub_peers_network_addresses:
            address = ipaddress.IPv4Network(
                f"{max(hub_peers_network_addresses) + 1}/32"
            )
        else:
            address = ipaddress.IPv4Network(
                f"{hub.tunnel.interface.address.network_address + 1}/32"
            )
        private_key: str = wg.gen_priv_key()
        # TODO: what do we do for listen port? It should only be on the
        # interface btw It looks like we shouldn't set it:
        # https://github.com/pirate/wireguard-docs#ListenPort
        interface: Interface = Interface(
            name,
            private_key,
            address,
            dns=[hub.tunnel.interface.address.network_address],
        )
        # NOTE: We can only assumes that peers will be road-warriors, hence
        # will be connecting to the Hub from behind NATs. As a result, we set a
        # persistent keepalive to 25 seconds.
        as_peer: Peer = Peer(
            name,
            wg.gen_pub_key(private_key),
            allowed_ips=[address],
            persistent_keepalive=25,
        )
        tunnel: Tunnel = Tunnel(name, interface, [hub.as_peer])
        # add peer to hub's tunnel
        hub.tunnel.add_peer(as_peer)
        # TODO: if tunnel is up, should we do the update as in the legacy thing?
        return Spoke(name, tunnel, as_peer, hub)
