import os
import re
import shlex

from im360.subsys.webshield import service_reload

_DOMAIN_PATTERN = re.compile(
    r"^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|"
    r"([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|"
    r"([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\."
    r"([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$"
)


class _Comment:
    @classmethod
    def parse(cls, raw):
        raw = raw.strip()
        if not raw.startswith("#"):
            return
        message = raw.lstrip("#")
        message = message.strip()
        return cls(message)

    def __init__(self, message):
        self.message = message

    def __repr__(self):
        return "<Comment({message})>".format(message=repr(self.message))

    def __str__(self):
        return "# {message}".format(message=self.message)


class Host:
    @classmethod
    def parse(cls, raw):
        data = shlex.split(raw)
        try:
            name, state = data[:2]
        except ValueError:
            return
        state = state.rstrip(";")
        state = 0 if state in ("", "0") else 1
        try:
            return cls(name, state)
        except ValueError:
            return

    def __init__(self, name: str, state=1):
        # wildcard: '.example.com'
        if name.startswith("."):
            domain = name.removeprefix(".")
        # wildcard: '*.example.com'
        elif name.startswith("*."):
            domain = name.removeprefix("*.")
        # raw domain
        else:
            domain = name
        if _DOMAIN_PATTERN.match(domain) is None and name != "*":
            raise ValueError(f"Invalid domain value '{name!s}':")
        self.name = name
        self.state = state

    def __eq__(self, other):
        return self.name == other.name

    def __hash__(self):
        return hash(self.name)

    def __repr__(self):
        return "<Host({name}, {state})>".format(
            name=repr(self.name), state=self.state
        )

    def __str__(self):
        return "{name} {state};".format(name=self.name, state=self.state)


class _Empty:
    @classmethod
    def parse(cls, raw):
        assert not raw.strip()
        return cls()

    def __repr__(self):
        return "<Empty()>"

    def __str__(self):
        return ""


class _WhitelistedDomainsConfig:
    WHITELISTED_DOMAINS_CONF = (
        "/etc/imunify360-webshield/whitelisted-domains.conf"
    )

    @classmethod
    def read(cls, filename=None):
        lines = []
        with open(filename or cls.WHITELISTED_DOMAINS_CONF) as r:
            for _line in r:
                try:
                    line = (
                        Host.parse(_line)
                        or _Comment.parse(_line)
                        or _Empty.parse(_line)
                    )
                except AssertionError:
                    raise ValueError("Error parsing line: {l}".format(l=_line))
                lines.append(line)
        return cls(lines)

    @property
    def list(self):
        return [line for line in self.lines if isinstance(line, Host)]

    def __init__(self, lines):
        self.lines = lines

    def _last_empty(self):
        if len(self.lines) == 0:
            return
        if not isinstance(self.lines[-1], (_Empty, Host)):
            self.lines.append(_Empty())

    def add(self, host):
        if host in self.list:
            raise ValueError(
                "Domain already whitelisted: {}".format(host.name)
            )

        self._last_empty()
        self.lines.append(host)

    def flush(self):
        lines = [line for line in self.lines if not isinstance(line, Host)]
        self.lines = lines

    def remove(self, host):
        if host not in self.list:
            return

        lines = []
        for line in self.lines:
            if isinstance(line, Host) and line == host:
                continue
            lines.append(line)

        while isinstance(lines[-1], _Empty):
            lines = lines[:-1]

        self.lines = lines

    def sync(self, hosts):
        self.flush()
        self._last_empty()
        self.lines.extend(set(hosts))

    def write(self, filename=None):
        with open(filename or self.WHITELISTED_DOMAINS_CONF, "w") as w:
            for line in self.lines:
                w.write(str(line) + os.linesep)


def get_list():
    wl = _WhitelistedDomainsConfig.read()
    return [li.name for li in wl.list]


async def add_domain(domain):
    wl = _WhitelistedDomainsConfig.read()
    wl.add(Host(domain))
    wl.write()
    await service_reload()


async def del_domain(domain):
    wl = _WhitelistedDomainsConfig.read()
    wl.remove(Host(domain))
    wl.write()
    await service_reload()


async def reset_domains(domains):
    wl = _WhitelistedDomainsConfig.read()
    wl.sync([Host(d) for d in domains])
    wl.write()
    await service_reload()
