"""
AdGuard Home Manager — runs adguard/adguardhome in a Docker container,
joined to the same internal `amnezia-dns-net` network the rest of the panel
uses. Two install modes:

  * 'replace'    — removes the AmneziaDNS container (if present) and takes
                   its static IP (172.29.172.254) so VPN clients keep using
                   the same upstream.
  * 'sidebyside' — runs alongside AmneziaDNS on a different static IP
                   (172.29.172.253). VPN users can hit it on demand
                   (e.g. via the web admin UI from inside the tunnel).

Initial setup of AdGuard itself runs through its built-in wizard on the web
UI port — we do not try to script it (the JSON setup API is unstable across
versions and trying to drive it programmatically tends to break on upgrade).
"""

import logging

logger = logging.getLogger(__name__)


class AdguardManager:
    PROTOCOL = 'adguard'
    CONTAINER_NAME = 'amnezia-adguard'
    IMAGE_NAME = 'adguard/adguardhome:latest'

    NETWORK_NAME = 'amnezia-dns-net'
    NETWORK_SUBNET = '172.29.172.0/24'
    REPLACE_IP = '172.29.172.254'      # AmneziaDNS's slot
    SIDEBYSIDE_IP = '172.29.172.253'   # parallel to AmneziaDNS

    HOST_DIR = '/opt/amnezia/adguard'
    DEFAULT_DNS_PORT = 53
    DEFAULT_WEB_PORT = 3000
    DEFAULT_DOT_PORT = 853
    DEFAULT_DOH_PORT = 443

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

    # ===================== STATUS =====================

    def check_docker_installed(self):
        out, _, code = self.ssh.run_command("docker --version 2>/dev/null")
        if code != 0:
            return False
        out2, _, _ = self.ssh.run_command(
            "systemctl is-active docker 2>/dev/null || service docker status 2>/dev/null"
        )
        return 'active' in out2 or 'running' in out2.lower()

    def check_protocol_installed(self, protocol_type='adguard'):
        out, _, _ = self.ssh.run_sudo_command(
            f"docker ps -a --filter name=^{self.CONTAINER_NAME}$ --format '{{{{.Names}}}}'"
        )
        return self.CONTAINER_NAME in out.strip().split('\n')

    def check_container_running(self):
        out, _, _ = self.ssh.run_sudo_command(
            f"docker ps --filter name=^{self.CONTAINER_NAME}$ --format '{{{{.Status}}}}'"
        )
        return 'Up' in out

    def _container_ip(self):
        out, _, _ = self.ssh.run_sudo_command(
            f"docker inspect -f '{{{{range .NetworkSettings.Networks}}}}{{{{.IPAddress}}}} {{{{end}}}}' {self.CONTAINER_NAME} 2>/dev/null"
        )
        ip = out.strip().split()[0] if out.strip() else ''
        return ip

    def _detect_mode(self):
        """Return 'replace' or 'sidebyside' based on the running container's IP.
        Returns None if not detectable (container not running)."""
        ip = self._container_ip()
        if ip == self.REPLACE_IP:
            return 'replace'
        if ip == self.SIDEBYSIDE_IP:
            return 'sidebyside'
        return None

    def _container_web_port(self):
        """Read the AdGuard HTTP address configured inside the container.
        During the first-run wizard AdGuard writes web.listen_addresses to
        AdGuardHome.yaml. If the wizard has not been completed yet, the
        container is started with --web-addr 0.0.0.0:<port> and this method can
        read that port from docker inspect."""
        grep_cmd = "grep -E '^[[:space:]]*-?[[:space:]]*[0-9.]+:[0-9]+$' /opt/adguardhome/conf/AdGuardHome.yaml 2>/dev/null | head -n1"
        out, _, _ = self.ssh.run_sudo_command(
            f'docker exec {self.CONTAINER_NAME} sh -c "{grep_cmd}"'
        )
        line = out.strip().split('\n')[0].strip().lstrip('-').strip() if out.strip() else ''
        if ':' in line:
            try:
                return int(line.rsplit(':', 1)[1])
            except ValueError:
                pass

        out, _, _ = self.ssh.run_sudo_command(
            f"docker inspect -f '{{{{json .Config.Cmd}}}}' {self.CONTAINER_NAME} 2>/dev/null"
        )
        marker = '--web-addr'
        if marker in out:
            parts = out.replace('[', ' ').replace(']', ' ').replace(',', ' ').replace('\"', ' ').split()
            for i, part in enumerate(parts):
                if part == marker and i + 1 < len(parts) and ':' in parts[i + 1]:
                    try:
                        return int(parts[i + 1].rsplit(':', 1)[1])
                    except ValueError:
                        pass
        return None

    def _exposed_web_port(self, container_port=None):
        """Reads back the host->container port mapping for the web UI port,
        so the panel can show the correct admin URL after install."""
        container_port = int(container_port or self.DEFAULT_WEB_PORT)
        for port in (container_port, self.DEFAULT_WEB_PORT):
            out, _, _ = self.ssh.run_sudo_command(
                f"docker port {self.CONTAINER_NAME} {port}/tcp 2>/dev/null"
            )
            if not out.strip():
                continue
            # output like "0.0.0.0:3000" — take the last colon-separated chunk
            last = out.strip().split('\n')[0].split(':')[-1].strip()
            try:
                return int(last)
            except ValueError:
                continue
        return None

    def get_server_status(self, protocol_type='adguard'):
        exists = self.check_protocol_installed()
        running = self.check_container_running()
        mode = self._detect_mode() if running else None
        ip = self._container_ip() if running else ''
        container_web_port = self._container_web_port() if running else None
        exposed_port = self._exposed_web_port(container_web_port) if running else None
        # When the web UI is not bound to the host the user still needs an
        # admin URL (reachable via VPN) — use the actual container web port
        # when known, otherwise fall back to AdGuard's default :3000.
        return {
            'container_exists': exists,
            'container_running': running,
            'mode': mode,
            'internal_ip': ip,
            'web_port': exposed_port or container_web_port or self.DEFAULT_WEB_PORT,
            'web_exposed': exposed_port is not None,
            'port': self.DEFAULT_DNS_PORT,
            'protocol': protocol_type,
        }

    # ===================== INSTALL / REMOVE =====================

    def _ensure_network(self):
        self.ssh.run_sudo_command(
            f"docker network ls | grep -q {self.NETWORK_NAME} || "
            f"docker network create --subnet {self.NETWORK_SUBNET} {self.NETWORK_NAME}"
        )

    def install_protocol(
        self,
        protocol_type='adguard',
        mode='sidebyside',
        web_port=None,
        expose_web=False,
        dns_port=None,
        dot_port=None,
        doh_port=None,
        expose_dns=False,
        expose_dot=False,
        expose_doh=False,
    ):
        if not self.check_docker_installed():
            return {'status': 'error', 'message': 'Docker not installed'}
        if mode not in ('replace', 'sidebyside'):
            return {'status': 'error', 'message': f"Invalid mode '{mode}'"}

        web_port = int(web_port or self.DEFAULT_WEB_PORT)
        dns_port = int(dns_port or self.DEFAULT_DNS_PORT)
        dot_port = int(dot_port or self.DEFAULT_DOT_PORT)
        doh_port = int(doh_port or self.DEFAULT_DOH_PORT)

        # Persistent volumes — without these the AdGuard setup wizard would have
        # to re-run on every container recreate.
        self.ssh.run_sudo_command(f"mkdir -p {self.HOST_DIR}/work {self.HOST_DIR}/conf")

        self._ensure_network()

        # Replace mode: detach + remove AmneziaDNS so we can claim its IP.
        if mode == 'replace':
            self.ssh.run_sudo_command(
                f"docker network disconnect {self.NETWORK_NAME} amnezia-dns 2>/dev/null || true"
            )
            self.ssh.run_sudo_command("docker stop amnezia-dns 2>/dev/null || true")
            self.ssh.run_sudo_command("docker rm -fv amnezia-dns 2>/dev/null || true")
            target_ip = self.REPLACE_IP
        else:
            target_ip = self.SIDEBYSIDE_IP

        if self.check_protocol_installed():
            self.ssh.run_sudo_command(f"docker stop {self.CONTAINER_NAME} 2>/dev/null || true")
            self.ssh.run_sudo_command(f"docker rm -fv {self.CONTAINER_NAME} 2>/dev/null || true")

        self.ssh.run_sudo_command(f"docker pull {self.IMAGE_NAME}")

        # Build port mapping. By default ports are reachable only inside
        # `amnezia-dns-net` (so VPN clients hit them via the static IP).
        # Optional `expose_*` flags add host port mappings for direct access.
        ports = []
        if expose_web:
            ports.append(f"-p {web_port}:{web_port}/tcp")
        if expose_dns:
            ports.append(f"-p {dns_port}:53/tcp")
            ports.append(f"-p {dns_port}:53/udp")
        if expose_dot:
            ports.append(f"-p {dot_port}:853/tcp")
        if expose_doh:
            ports.append(f"-p {doh_port}:443/tcp")
        ports_str = ' '.join(ports)

        run_cmd = (
            f"docker run -d --name {self.CONTAINER_NAME} --restart always "
            f"--network {self.NETWORK_NAME} --ip {target_ip} "
            f"-v {self.HOST_DIR}/work:/opt/adguardhome/work "
            f"-v {self.HOST_DIR}/conf:/opt/adguardhome/conf "
            f"{ports_str} "
            f"{self.IMAGE_NAME} --web-addr 0.0.0.0:{web_port}"
        )
        _, err, code = self.ssh.run_sudo_command(run_cmd)
        if code != 0:
            return {'status': 'error', 'message': f'Failed to start container: {err}'}

        # Re-attach known VPN containers to the DNS network so they can reach
        # AdGuard at target_ip (mirrors what dns_manager.py does on install).
        for c in ('amnezia-awg', 'amnezia-awg2', 'amnezia-awg-legacy', 'amnezia-xray', 'amnezia-wireguard', 'telemt'):
            self.ssh.run_sudo_command(
                f"docker ps --format '{{{{.Names}}}}' | grep -q '^{c}$' && "
                f"docker network connect {self.NETWORK_NAME} {c} 2>/dev/null || true"
            )

        url_host = self.ssh.host if expose_web else target_ip
        admin_url = f"http://{url_host}:{web_port}"
        return {
            'status': 'success',
            'protocol': 'adguard',
            'mode': mode,
            'internal_ip': target_ip,
            'web_port': web_port,
            'expose_web': bool(expose_web),
            'admin_url': admin_url,
            'message': 'AdGuard Home installed. Complete the setup wizard via the web UI.',
            'log': [
                f"AdGuard Home installed in '{mode}' mode",
                f"Internal IP: {target_ip}",
                f"Admin UI: {admin_url}" + ("" if expose_web else "  (VPN-only — connect via VPN to reach it)"),
                'Open the URL above to run the AdGuard setup wizard.',
            ],
        }

    def remove_container(self, protocol_type='adguard'):
        self.ssh.run_sudo_command(f"docker stop {self.CONTAINER_NAME} || true")
        self.ssh.run_sudo_command(f"docker rm -fv {self.CONTAINER_NAME} || true")
        self.ssh.run_sudo_command(f"rm -rf {self.HOST_DIR}")
        return True
