import ipaddress
import urllib.parse
from dataclasses import KW_ONLY, dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional, cast

from fugue.lib import types, utils


@dataclass
class Peer:
    """
    Peer here relates to a remote peer's configuration, instead of a computer
    acting as a peer in a network.

    For configuration reference see: https://github.com/pirate/wireguard-docs#Peer.
    """

    name: str
    public_key: str
    allowed_ips: List[ipaddress.IPv4Network]
    _: KW_ONLY
    endpoint: Optional[types.URL] = None
    persistent_keepalive: Optional[int] = None

    @staticmethod
    def from_dict(_dict: types.PeerDict):
        return Peer(
            _dict["name"],
            _dict["public_key"],
            _dict["allowed_ips"],
            endpoint=_dict.get("endpoint"),
            persistent_keepalive=_dict.get("persistent_keepalive"),
        )

    @staticmethod
    def parse_conf_line(acc: Dict[str, Any], line: str) -> Dict[str, Any]:
        # update accumulator with [Peer] section line
        if line.startswith("# Name"):
            acc["name"] = utils.ini_after_equal(line)

        elif line.startswith("PublicKey"):
            acc["public_key"] = utils.ini_after_equal(line)

        elif line.startswith("AllowedIPs"):
            acc["allowed_ips"] = [
                ipaddress.IPv4Network(ip.strip())
                for ip in utils.ini_after_equal(line).split(",")
            ]

        elif line.startswith("Endpoint"):
            acc["endpoint"] = urllib.parse.urlparse(utils.ini_after_equal(line))

        elif line.startswith("PersistentKeepalive"):
            acc["persistent_keepalive"] = int(utils.ini_after_equal(line))

        return acc

    @staticmethod
    def from_conf(conf_fp: Path) -> "Peer":
        with open(conf_fp, "r") as fd:
            conf_lines = fd.readlines()

        # init vars
        in_peer_section: bool = False

        # accumulator
        acc: Dict[str, Any] = {}

        # cleaning
        conf_lines_cleaned = [
            line.replace("\n", "").strip()  # newline chars & strip
            for line in conf_lines
        ]
        conf_lines_cleaned = [
            line
            for line in conf_lines_cleaned
            # empty lines & comments, except for Name line
            if line and (not line.startswith("#") or line.startswith("# Name"))
        ]

        for line in conf_lines_cleaned:
            # init
            if not in_peer_section:
                if line.startswith("[Peer]"):
                    in_peer_section = True

                else:
                    raise RuntimeError(
                        f"error: parsing peer conf file: '{conf_fp}'"
                    )

            # parse a peer section
            elif in_peer_section:
                # error: entering unrecognised section
                if line.startswith("["):
                    raise RuntimeError(
                        f"error: unrecognised section: '{line}' in interface file: '{conf_fp}'"
                    )

                else:
                    # update accumulator with [Peer] section line
                    acc = Peer.parse_conf_line(acc, line)

        return Peer.from_dict(cast(types.PeerDict, acc))

    def as_ini(self, print_header=True) -> str:
        ini_str: str
        if print_header:
            ini_str = "# Generated by fugue\n" "[Peer]\n"

        else:
            ini_str = "[Peer]\n"

        if self.name:
            ini_str += f"# Name = {self.name}\n"

        ini_str += (
            f"PublicKey = {self.public_key}\n"
            f"AllowedIPs = {','.join([str(aip) for aip in self.allowed_ips])}\n"
        )

        if self.endpoint:
            ini_str += f"Endpoint = {self.endpoint.geturl()}\n"

        if self.persistent_keepalive:
            ini_str += f"PersistentKeepalive = {self.persistent_keepalive}\n"

        return ini_str
