"""Windows network latency optimizer — Nagle, TCP tuning, adapter settings."""
from __future__ import annotations

import logging
import re
from typing import Optional

from mesh_boost.optimizer.base import (
    IS_WINDOWS, OptimizationResult, RollbackManager,
    is_admin, reg_read, reg_write, require_admin, run_cmd, run_powershell,
)

logger = logging.getLogger(__name__)

# Registry paths
TCPIP_PARAMS = r"HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters"
TCPIP_INTERFACES = r"HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces"
AFD_PARAMS = r"HKLM\SYSTEM\CurrentControlSet\Services\AFD\Parameters"


class NetworkOptimizer:
    """Optimize Windows network settings for minimum latency."""

    def __init__(self):
        self._rollback = RollbackManager("network")

    def apply(self, dry_run: bool = False) -> OptimizationResult:
        """Apply all network optimizations."""
        result = OptimizationResult("network")

        if not IS_WINDOWS:
            result.skip("all", "not Windows")
            return result

        self._apply_nagle_disable(result, dry_run)
        self._apply_tcp_tuning(result, dry_run)
        self._apply_network_throttling(result, dry_run)
        self._apply_adapter_settings(result, dry_run)
        self._apply_rss(result, dry_run)

        return result

    def restore(self) -> OptimizationResult:
        """Restore all original network settings."""
        result = OptimizationResult("network_restore")

        if not IS_WINDOWS:
            return result

        self._restore_nagle(result)
        self._restore_tcp_tuning(result)
        self._restore_network_throttling(result)
        self._restore_adapter_settings(result)

        self._rollback.clear()
        return result

    # ── Nagle's Algorithm ────────────────────────────────────────────────

    def _apply_nagle_disable(self, result: OptimizationResult, dry_run: bool):
        """Disable Nagle's algorithm on all active network interfaces.

        Nagle buffers small TCP packets (up to 200ms) to coalesce them.
        Great for throughput, terrible for latency. Disabling it sends
        packets immediately — critical for gaming and real-time apps.
        """
        if not require_admin("nagle_disable"):
            result.skip("nagle", "not admin")
            return

        if dry_run:
            result.skip("nagle", "dry_run")
            return

        interfaces = self._get_active_interfaces()
        if not interfaces:
            result.skip("nagle", "no active interfaces found")
            return

        changed = 0
        for iface_guid in interfaces:
            iface_key = f"{TCPIP_INTERFACES}\\{iface_guid}"

            # Save originals
            orig_nodelay = reg_read(iface_key, "TcpNoDelay")
            orig_ackfreq = reg_read(iface_key, "TcpAckFrequency")
            self._rollback.save_original(f"nagle_{iface_guid}_nodelay", orig_nodelay)
            self._rollback.save_original(f"nagle_{iface_guid}_ackfreq", orig_ackfreq)

            # TcpNoDelay=1 disables Nagle
            if reg_write(iface_key, "TcpNoDelay", 1):
                changed += 1

            # TcpAckFrequency=1 sends ACKs immediately (default=2, waits for second packet)
            if reg_write(iface_key, "TcpAckFrequency", 1):
                changed += 1

        if changed > 0:
            result.ok(f"nagle=disabled on {len(interfaces)} interfaces ({changed} reg writes)")
        else:
            result.fail("nagle", "no registry writes succeeded")

    def _restore_nagle(self, result: OptimizationResult):
        interfaces = self._get_active_interfaces()
        restored = 0
        for iface_guid in interfaces:
            iface_key = f"{TCPIP_INTERFACES}\\{iface_guid}"
            for suffix, name in [("nodelay", "TcpNoDelay"), ("ackfreq", "TcpAckFrequency")]:
                orig = self._rollback.get_original(f"nagle_{iface_guid}_{suffix}")
                if orig is not None:
                    if reg_write(iface_key, name, orig):
                        restored += 1
        if restored:
            result.ok(f"nagle restored ({restored} values)")

    # ── TCP Global Tuning ────────────────────────────────────────────────

    def _apply_tcp_tuning(self, result: OptimizationResult, dry_run: bool):
        """Tune TCP stack parameters for low latency.

        Key settings:
        - Auto-tuning level: normal → restricted (prevents buffer bloat)
        - Congestion provider: ctcp for faster recovery
        - ECN: disable (some routers drop ECN-marked packets)
        - Timestamps: disable (saves 12 bytes per packet header)
        """
        if not require_admin("tcp_tuning"):
            result.skip("tcp_tuning", "not admin")
            return

        if dry_run:
            result.skip("tcp_tuning", "dry_run")
            return

        changes = 0

        # Get current auto-tuning level
        ok, output = run_cmd(["netsh", "int", "tcp", "show", "global"])
        if ok:
            self._rollback.save_original("tcp_global_output", output)

        # Set auto-tuning to restricted (reduces buffer bloat without disabling)
        ok, _ = run_cmd([
            "netsh", "int", "tcp", "set", "global",
            "autotuninglevel=restricted",
        ])
        if ok:
            changes += 1

        # Disable ECN (Explicit Congestion Notification)
        ok, _ = run_cmd([
            "netsh", "int", "tcp", "set", "global", "ecncapability=disabled",
        ])
        if ok:
            changes += 1

        # Disable TCP timestamps (saves 12 bytes/packet, reduces latency)
        ok, _ = run_cmd([
            "netsh", "int", "tcp", "set", "global", "timestamps=disabled",
        ])
        if ok:
            changes += 1

        # Enable Direct Cache Access if supported
        ok, _ = run_cmd([
            "netsh", "int", "tcp", "set", "global", "dca=enabled",
        ])
        if ok:
            changes += 1

        # TCP global params via registry
        tcp_settings = {
            # Default TTL (default 128, set to 64 for faster routing)
            (TCPIP_PARAMS, "DefaultTTL"): 64,
            # Max SYN retransmissions (default 2, reduce for faster fail)
            (TCPIP_PARAMS, "TcpMaxConnectRetransmissions"): 1,
            # Disable TCP selective ACKs for lower overhead
            (TCPIP_PARAMS, "SackOpts"): 0,
            # Max ports available for outbound connections
            (TCPIP_PARAMS, "MaxUserPort"): 65534,
            # Faster TIME_WAIT recycling (default 240s)
            (TCPIP_PARAMS, "TcpTimedWaitDelay"): 30,
        }

        for (key, name), value in tcp_settings.items():
            orig = reg_read(key, name)
            self._rollback.save_original(f"tcp_{name}", orig)
            if reg_write(key, name, value):
                changes += 1

        if changes > 0:
            result.ok(f"tcp_tuning={changes} settings (restricted autotuning, ECN off, timestamps off)")
        else:
            result.fail("tcp_tuning", "no changes applied")

    def _restore_tcp_tuning(self, result: OptimizationResult):
        restored = 0

        # Restore netsh settings to defaults
        for setting, default in [
            ("autotuninglevel", "normal"),
            ("ecncapability", "default"),
            ("timestamps", "default"),
        ]:
            ok, _ = run_cmd(["netsh", "int", "tcp", "set", "global", f"{setting}={default}"])
            if ok:
                restored += 1

        # Restore registry values
        for name in ["DefaultTTL", "TcpMaxConnectRetransmissions", "SackOpts",
                      "MaxUserPort", "TcpTimedWaitDelay"]:
            orig = self._rollback.get_original(f"tcp_{name}")
            if orig is not None:
                if reg_write(TCPIP_PARAMS, name, orig):
                    restored += 1

        if restored:
            result.ok(f"tcp_tuning restored ({restored} settings)")

    # ── Network Throttling ───────────────────────────────────────────────

    def _apply_network_throttling(self, result: OptimizationResult, dry_run: bool):
        """Disable Windows network throttling index.

        Windows throttles network traffic for multimedia playback by default
        (NetworkThrottlingIndex=10). Disabling this removes the 10-packet
        throttle and allows full throughput.
        """
        if not require_admin("network_throttling"):
            result.skip("network_throttling", "not admin")
            return

        if dry_run:
            result.skip("network_throttling", "dry_run")
            return

        mmcss_key = r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile"

        orig = reg_read(mmcss_key, "NetworkThrottlingIndex")
        self._rollback.save_original("network_throttling_index", orig)

        # 0xFFFFFFFF = disable throttling entirely
        if reg_write(mmcss_key, "NetworkThrottlingIndex", 0xFFFFFFFF):
            result.ok("network_throttling=disabled (0xFFFFFFFF)")
        else:
            result.fail("network_throttling", "registry write failed")

    def _restore_network_throttling(self, result: OptimizationResult):
        mmcss_key = r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile"
        orig = self._rollback.get_original("network_throttling_index")
        if orig is not None:
            if reg_write(mmcss_key, "NetworkThrottlingIndex", orig):
                result.ok("network_throttling restored")

    # ── Adapter Settings ─────────────────────────────────────────────────

    def _apply_adapter_settings(self, result: OptimizationResult, dry_run: bool):
        """Tune NIC adapter settings for low latency.

        Interrupt moderation coalesces interrupts to reduce CPU usage, but
        adds latency. Disabling it on gaming NICs reduces network latency
        by 0.5-2ms at the cost of slightly higher CPU usage.
        """
        if not require_admin("adapter_settings"):
            result.skip("adapter_settings", "not admin")
            return

        if dry_run:
            result.skip("adapter_settings", "dry_run")
            return

        adapters = self._get_adapter_names()
        if not adapters:
            result.skip("adapter_settings", "no adapters found")
            return

        changed = 0
        for adapter in adapters:
            # Disable interrupt moderation (adds latency)
            ok, _ = run_powershell(
                f'Set-NetAdapterAdvancedProperty -Name "{adapter}" '
                f'-RegistryKeyword "*InterruptModeration" -RegistryValue 0 '
                f'-ErrorAction SilentlyContinue'
            )
            if ok:
                changed += 1
                self._rollback.save_original(f"adapter_{adapter}_intmod", 1)

            # Set receive buffers to maximum
            ok, _ = run_powershell(
                f'Set-NetAdapterAdvancedProperty -Name "{adapter}" '
                f'-RegistryKeyword "*ReceiveBuffers" -RegistryValue 2048 '
                f'-ErrorAction SilentlyContinue'
            )
            if ok:
                changed += 1

            # Set transmit buffers to maximum
            ok, _ = run_powershell(
                f'Set-NetAdapterAdvancedProperty -Name "{adapter}" '
                f'-RegistryKeyword "*TransmitBuffers" -RegistryValue 2048 '
                f'-ErrorAction SilentlyContinue'
            )
            if ok:
                changed += 1

        if changed > 0:
            result.ok(f"adapter_settings={changed} changes on {len(adapters)} adapters")
        else:
            result.skip("adapter_settings", "no adapter changes applied (may not support these properties)")

    def _restore_adapter_settings(self, result: OptimizationResult):
        adapters = self._get_adapter_names()
        restored = 0
        for adapter in adapters:
            orig = self._rollback.get_original(f"adapter_{adapter}_intmod")
            if orig is not None:
                ok, _ = run_powershell(
                    f'Set-NetAdapterAdvancedProperty -Name "{adapter}" '
                    f'-RegistryKeyword "*InterruptModeration" -RegistryValue {orig} '
                    f'-ErrorAction SilentlyContinue'
                )
                if ok:
                    restored += 1
        if restored:
            result.ok(f"adapter_settings restored ({restored} adapters)")

    # ── Receive Side Scaling (RSS) ───────────────────────────────────────

    def _apply_rss(self, result: OptimizationResult, dry_run: bool):
        """Enable and optimize Receive Side Scaling.

        RSS distributes network receive processing across multiple CPU cores.
        Without RSS, all network interrupts hit core 0, creating a bottleneck.
        """
        if not require_admin("rss"):
            result.skip("rss", "not admin")
            return

        if dry_run:
            result.skip("rss", "dry_run")
            return

        adapters = self._get_adapter_names()
        changed = 0

        for adapter in adapters:
            # Enable RSS
            ok, _ = run_powershell(
                f'Enable-NetAdapterRss -Name "{adapter}" -ErrorAction SilentlyContinue'
            )
            if ok:
                changed += 1

            # Set RSS profile to closest processor (lowest latency)
            ok, _ = run_powershell(
                f'Set-NetAdapterRss -Name "{adapter}" '
                f'-Profile Closest -ErrorAction SilentlyContinue'
            )
            if ok:
                changed += 1

        if changed > 0:
            result.ok(f"rss=enabled on {len(adapters)} adapters (Closest profile)")
        else:
            result.skip("rss", "no adapters support RSS")

    # ── Status ───────────────────────────────────────────────────────────

    def get_status(self) -> dict:
        """Return current network optimization status."""
        status = {"module": "network", "optimized": False, "settings": {}}

        if not IS_WINDOWS:
            return status

        # Check TCP auto-tuning level
        ok, output = run_cmd(["netsh", "int", "tcp", "show", "global"])
        if ok:
            m = re.search(r"Receive Window Auto-Tuning Level\s*:\s*(\w+)", output)
            if m:
                level = m.group(1).lower()
                status["settings"]["autotuning"] = level
                status["settings"]["autotuning_optimized"] = level in ("restricted", "disabled")

            m = re.search(r"ECN Capability\s*:\s*(\w+)", output)
            if m:
                status["settings"]["ecn"] = m.group(1).lower()

        # Check Nagle on active interfaces
        interfaces = self._get_active_interfaces()
        nagle_disabled = 0
        for iface_guid in interfaces:
            iface_key = f"{TCPIP_INTERFACES}\\{iface_guid}"
            val = reg_read(iface_key, "TcpNoDelay")
            if val == 1:
                nagle_disabled += 1
        status["settings"]["nagle_disabled_interfaces"] = nagle_disabled
        status["settings"]["total_interfaces"] = len(interfaces)

        status["optimized"] = self._rollback.has_saved_state
        return status

    # ── Helpers ───────────────────────────────────────────────────────────

    def _get_active_interfaces(self) -> list[str]:
        """Get GUIDs of all active network interfaces with IP addresses."""
        if not IS_WINDOWS:
            return []

        try:
            import winreg
            guids = []
            hive, subkey = _parse_interfaces_key()
            with winreg.OpenKey(hive, subkey) as key:
                i = 0
                while True:
                    try:
                        guid = winreg.EnumKey(key, i)
                        # Check if this interface has an IP address (is active)
                        iface_key = f"{TCPIP_INTERFACES}\\{guid}"
                        ip = reg_read(iface_key, "DhcpIPAddress")
                        static_ip = reg_read(iface_key, "IPAddress")
                        if (ip and ip != "0.0.0.0") or (isinstance(static_ip, list) and static_ip and static_ip[0] != "0.0.0.0"):
                            guids.append(guid)
                        i += 1
                    except OSError:
                        break
            return guids
        except Exception as e:
            logger.debug("Failed to enumerate interfaces: %s", e)
            return []

    def _get_adapter_names(self) -> list[str]:
        """Get names of active network adapters via PowerShell."""
        ok, output = run_powershell(
            "Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | "
            "Select-Object -ExpandProperty Name"
        )
        if ok and output:
            return [name.strip() for name in output.splitlines() if name.strip()]
        return []


def _parse_interfaces_key():
    """Parse the interfaces registry key path."""
    import winreg
    return winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces"
