import ipaddress
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 Interface:
    """
    A tunnel's interface.

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

    name: str
    private_key: str
    address: ipaddress.IPv4Network
    _: KW_ONLY
    listen_port: Optional[int] = None
    dns: Optional[List[ipaddress.IPv4Address]] = None
    table: Optional[str] = None
    mtu: Optional[int] = None
    pre_ups: Optional[List[str]] = None
    post_ups: Optional[List[str]] = None
    pre_downs: Optional[List[str]] = None
    post_downs: Optional[List[str]] = None

    @staticmethod
    def from_dict(
        _dict: types.InterfaceDict,
    ) -> "Interface":  # https://stackoverflow.com/q/44640479
        return Interface(
            _dict["name"],
            _dict["private_key"],
            _dict["address"],
            listen_port=_dict.get("listen_port"),
            dns=_dict.get("dns"),
            table=_dict.get("table"),
            mtu=_dict.get("mtu"),
            pre_ups=_dict.get("pre_ups"),
            post_ups=_dict.get("post_ups"),
            pre_downs=_dict.get("pre_downs"),
            post_downs=_dict.get("post_downs"),
        )

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

        elif line.startswith("Address"):
            acc["address"] = ipaddress.IPv4Network(utils.ini_after_equal(line))

        elif line.startswith("ListenPort"):
            acc["listen_port"] = int(utils.ini_after_equal(line))

        elif line.startswith("PrivateKey"):
            acc["private_key"] = utils.ini_after_equal(line)

        elif line.startswith("DNS"):
            acc["dns"] = [
                ipaddress.IPv4Address(dns.strip())
                for dns in utils.ini_after_equal(line).split(",")
            ]

        elif line.startswith("Table"):
            acc["table"] = utils.ini_after_equal(line)

        elif line.startswith("MTU"):
            acc["mtu"] = int(utils.ini_after_equal(line))

        elif line.startswith("PreUp"):
            acc.setdefault("pre_ups", []).append(utils.ini_after_equal(line))

        elif line.startswith("PostUp"):
            acc.setdefault("post_ups", []).append(utils.ini_after_equal(line))

        elif line.startswith("PreDown"):
            acc.setdefault("pre_downs", []).append(utils.ini_after_equal(line))

        elif line.startswith("PostDown"):
            acc.setdefault("post_downs", []).append(utils.ini_after_equal(line))

        return acc

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

        # init vars
        in_interface_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_interface_section:
                if line.startswith("[Interface]"):
                    in_interface_section = True

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

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

                else:
                    acc = Interface.parse_conf_line(acc, line)

        return Interface.from_dict(cast(types.InterfaceDict, acc))

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

        else:
            ini_str = "[Interface]\n"

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

        ini_str += f"PrivateKey = {self.private_key}\n"
        ini_str += f"Address = {self.address}\n"

        if self.listen_port:
            ini_str += f"ListenPort = {self.listen_port}\n"

        if self.dns:
            ini_str += f"DNS = {', '.join([str(d) for d in self.dns])}\n"

        if self.table:
            ini_str += f"Table = {self.table}\n"

        if self.mtu:
            ini_str += f"MTU = {self.mtu}\n"

        if self.pre_ups:
            for pre_up in self.pre_ups:
                ini_str += f"PreUp = {pre_up}\n"

        if self.post_ups:
            for post_up in self.post_ups:
                ini_str += f"PostUp = {post_up}\n"

        if self.pre_downs:
            for pre_down in self.pre_downs:
                ini_str += f"PreDown = {pre_down}\n"

        if self.post_downs:
            for post_down in self.post_downs:
                ini_str += f"PostDown = {post_down}\n"

        return ini_str
