"""WSL2 utility functions for cross-platform authentication support.

WSL2 cannot directly launch GUI applications without causing terminal corruption.
This module provides helpers to launch Windows Chrome from WSL and manage
the cross-boundary authentication flow.

Security Note:
--------------
This module launches Chrome with --remote-debugging-address=0.0.0.0 to allow
connections from the WSL2 virtual network. This differs from the standard
H-3 remediation (which restricts to 127.0.0.1) because WSL2 uses a virtual
network bridge that requires cross-boundary access.

Mitigations in place:
- Windows Firewall limits connections to LocalSubnet (WSL virtual network only)
- Temporary Chrome profiles are used and cleaned up after authentication
- Chrome remote debugging is only active during explicit nlm login --wsl
- No other network hosts can reach the debugging port

See: docs/SECURITY_REMEDIATION_PLAN.md (H-3) for original security context.
"""

import contextlib
import logging
import shutil
import subprocess
import time
from pathlib import Path

import httpx

logger = logging.getLogger(__name__)


DEFAULT_WSL_CDP_PORT = 9222
WINDOWS_CHROME_PATHS = [
    r"C:\Program Files\Google\Chrome\Application\chrome.exe",
    r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
]


def is_wsl() -> bool:
    """Detect if running inside Windows Subsystem for Linux.

    Returns:
        True if WSL environment detected, False otherwise.
    """
    # Check for WSLInterop file (existence indicates WSL2)
    wslinterop = Path("/proc/sys/fs/binfmt_misc/WSLInterop")
    if wslinterop.exists():
        return True

    # Check kernel version string for microsoft
    try:
        version = Path("/proc/version").read_text().lower()
        return "microsoft" in version or "wsl" in version
    except (OSError, FileNotFoundError):
        pass

    return False


def get_windows_host_ip() -> str | None:
    """Get the Windows host IP address from WSL.

    WSL2 uses a virtual network where the Windows host is the default gateway.
    We check multiple sources to find the correct IP.

    Returns:
        IP address string (e.g., "172.20.112.1") or None if not in WSL.
    """
    if not is_wsl():
        return None

    # Method 1: Get default gateway (most reliable for Chrome binding)
    try:
        result = subprocess.run(
            ["ip", "route"],
            capture_output=True,
            text=True,
            check=True,
        )
        for line in result.stdout.splitlines():
            if line.startswith("default via"):
                # Format: "default via 172.25.144.1 dev eth0"
                ip = line.split()[2]
                logger.debug(f"Windows host IP from default gateway: {ip}")
                return ip
    except (subprocess.CalledProcessError, IndexError, FileNotFoundError) as e:
        logger.debug(f"Could not get IP from default gateway: {e}")

    # Method 2: Fallback to resolv.conf nameserver
    try:
        result = subprocess.run(
            ["grep", "nameserver", "/etc/resolv.conf"],
            capture_output=True,
            text=True,
            check=True,
        )
        # Format: "nameserver 10.255.255.254"
        ip = result.stdout.strip().split()[1]
        logger.debug(f"Windows host IP from resolv.conf: {ip}")
        return ip
    except (subprocess.CalledProcessError, IndexError, FileNotFoundError) as e:
        logger.warning(f"Could not determine Windows host IP: {e}")
        return None


def find_windows_chrome() -> str | None:
    """Find Chrome executable path on Windows side from WSL.

    Searches common Chrome installation locations.

    Returns:
        Windows path (e.g., "C:\\...\\chrome.exe") or None if not found.
    """
    if not is_wsl():
        return None

    for path in WINDOWS_CHROME_PATHS:
        # Convert Windows path to WSL path (/mnt/c/...)
        wsl_path = Path("/mnt/c") / path[3:].replace("\\", "/")
        if wsl_path.exists():
            logger.debug(f"Found Windows Chrome at: {path}")
            return path

    # Fallback: Try to find via PATH
    try:
        result = subprocess.run(
            ["which", "chrome.exe"],
            capture_output=True,
            text=True,
        )
        if result.returncode == 0:
            windows_path = result.stdout.strip().replace("/mnt/c/", "C:\\").replace("/", "\\")
            logger.debug(f"Found Windows Chrome via PATH: {windows_path}")
            return windows_path
    except Exception as e:
        logger.debug(f"which chrome.exe failed: {e}")

    return None


def launch_windows_chrome(
    port: int = DEFAULT_WSL_CDP_PORT, debug: bool = False
) -> subprocess.Popen:
    """Launch Chrome on Windows side from WSL.

    Args:
        port: Remote debugging port to use.
        debug: If True, capture Chrome's stderr for diagnostics.

    Returns:
        subprocess.Popen handle to the Windows Chrome process.

    Raises:
        RuntimeError: If Chrome cannot be launched.
    """
    if not is_wsl():
        raise RuntimeError("Not running in WSL environment")

    # Check if Chrome is already running - this is a HARD REQUIREMENT
    # because Chrome uses a single-instance model
    try:
        result = subprocess.run(
            ["pgrep", "-f", "chrome.exe"],
            capture_output=True,
            text=True,
        )
        if result.returncode == 0 and result.stdout.strip():
            # Try taskkill to close Chrome
            try:
                subprocess.run(
                    ["taskkill", "/f", "/im", "chrome.exe"],
                    capture_output=True,
                    text=True,
                    timeout=10,
                )
                time.sleep(2)  # Wait for Chrome to close
                # Check again
                result2 = subprocess.run(
                    ["pgrep", "-f", "chrome.exe"],
                    capture_output=True,
                    text=True,
                )
                if result2.returncode == 0 and result2.stdout.strip():
                    raise RuntimeError(
                        "Chrome is already running and could not be closed. "
                        "\n\nPlease:"
                        "\n  1. Close ALL Chrome windows and tabs manually"
                        "\n  2. Run: taskkill /f /im chrome.exe  (in Windows PowerShell Admin)"
                        "\n  3. Then retry: nlm login --wsl"
                    )
            except Exception as e:
                if "Chrome is already running" in str(e):
                    raise
                logger.debug(f"Could not terminate existing Chrome: {e}")
    except RuntimeError:
        raise
    except Exception:
        pass  # pgrep might not be available, continue anyway

    chrome_path = find_windows_chrome()
    if not chrome_path:
        raise RuntimeError(
            "Chrome not found on Windows side. "
            "Common locations checked:\n  " + "\n  ".join(WINDOWS_CHROME_PATHS)
        )

    # Convert Windows path to WSL executable path
    # C:\Program Files\... -> /mnt/c/Program Files/...
    wsl_chrome = Path("/mnt/c") / chrome_path[3:].replace("\\", "/")

    # Create a temp profile directory on the WINDOWS filesystem.
    # Using WSL's /tmp causes "Profile error occurred" because Chrome
    # struggles with UNC paths (\\wsl.localhost\...) for its profile dir.
    import tempfile

    try:
        win_temp_base = subprocess.run(
            ["powershell.exe", "-Command", "echo $env:TEMP"],
            capture_output=True,
            text=True,
            check=True,
        ).stdout.strip()
        # Create a unique subdir name
        import uuid

        windows_temp = f"{win_temp_base}\\nlm-chrome-{uuid.uuid4().hex[:8]}"
        # Convert to WSL path for cleanup tracking
        temp_dir = subprocess.run(
            ["wslpath", "-u", windows_temp],
            capture_output=True,
            text=True,
            check=True,
        ).stdout.strip()
    except Exception:
        # Fallback to WSL temp (may cause profile error but won't crash)
        temp_dir = tempfile.mkdtemp(prefix="nlm-chrome-")
        windows_temp = subprocess.run(
            ["wslpath", "-w", temp_dir],
            capture_output=True,
            text=True,
            check=True,
        ).stdout.strip()

    args = [
        str(wsl_chrome),
        f"--remote-debugging-port={port}",
        f"--user-data-dir={windows_temp}",  # CRITICAL: Fresh profile for separate instance
        "--no-first-run",
        "--no-default-browser-check",
        "--disable-extensions",
        "--disable-background-networking",
        "--disable-background-timer-throttling",
        "--disable-backgrounding-occluded-windows",
        "--disable-breakpad",
        "--disable-component-update",
        "--disable-default-apps",
        "--disable-features=ChromeCleanup,TranslateUI,PrivacySandboxSettings4",
        "--disable-hang-monitor",
        "--disable-ipc-flooding-protection",
        "--disable-popup-blocking",
        "--disable-prompt-on-repost",
        "--disable-renderer-backgrounding",
        "--force-color-profile=srgb",
        "--metrics-recording-only",
        "--safebrowsing-disable-auto-update",
        "--password-store=basic",  # Don't try to use system password store
        "--use-mock-keychain",  # Use mock keychain on macOS/Windows
    ]

    stderr_arg = None if debug else subprocess.DEVNULL
    stdout_arg = None if debug else subprocess.DEVNULL

    logger.info(f"Launching Windows Chrome on port {port}")
    logger.debug(f"Chrome temp profile: {temp_dir}")
    try:
        process = subprocess.Popen(
            args,
            stdout=stdout_arg,
            stderr=stderr_arg,
            start_new_session=True,  # Prevent signal propagation
        )
        # Store temp_dir for cleanup in terminate_windows_chrome
        process._nlm_temp_dir = temp_dir  # type: ignore[attr-defined]
        logger.debug(f"Chrome process started: PID {process.pid}")
        return process
    except Exception as e:
        # Clean up temp dir on launch failure
        with contextlib.suppress(Exception):
            shutil.rmtree(temp_dir, ignore_errors=True)
        raise RuntimeError(f"Failed to launch Chrome: {e}") from e


def wait_for_cdp(cdp_url: str, timeout: int = 30) -> bool:
    """Wait for Chrome DevTools Protocol to be ready.

    Args:
        cdp_url: Full CDP HTTP URL (e.g., "http://172.20.112.1:9222")
        timeout: Maximum seconds to wait.

    Returns:
        True if CDP is ready, False if timeout.
    """
    import urllib.parse

    parsed = urllib.parse.urlparse(cdp_url)
    base_url = f"{parsed.scheme}://{parsed.hostname}:{parsed.port}"

    logger.debug(f"Waiting for CDP at {base_url}")
    start = time.time()
    last_error = None
    while time.time() - start < timeout:
        try:
            # Try without headers first (simplest approach)
            response = httpx.get(f"{base_url}/json", timeout=2, follow_redirects=True)
            if response.status_code == 200:
                logger.debug(f"CDP ready after {time.time() - start:.1f}s")
                return True
            else:
                logger.debug(f"CDP returned status {response.status_code}")
        except Exception as e:
            last_error = str(e)
            pass
        time.sleep(0.5)

    logger.warning(f"CDP not ready after {timeout}s (last error: {last_error})")
    return False


def terminate_windows_chrome(process: subprocess.Popen | None) -> bool:
    """Terminate a Windows Chrome process launched from WSL.

    Also cleans up the temporary Chrome profile directory if one was created.

    Args:
        process: subprocess.Popen handle from launch_windows_chrome()

    Returns:
        True if termination was attempted, False otherwise.
    """
    if process is None:
        return False

    # Get temp_dir before terminating (in case process._nlm_temp_dir is cleared)
    temp_dir = getattr(process, "_nlm_temp_dir", None)

    try:
        process.terminate()
        try:
            process.wait(timeout=5)
        except subprocess.TimeoutExpired:
            process.kill()
        logger.debug(f"Terminated Chrome process {process.pid}")
    except Exception as e:
        logger.warning(f"Failed to terminate Chrome: {e}")

    # Clean up temp profile directory
    if temp_dir:
        try:
            shutil.rmtree(temp_dir, ignore_errors=True)
            logger.debug(f"Cleaned up Chrome temp profile: {temp_dir}")
        except Exception as e:
            logger.debug(f"Failed to cleanup temp dir {temp_dir}: {e}")

    return True


def get_wsl_cdp_url(port: int = DEFAULT_WSL_CDP_PORT) -> str | None:
    """Get the CDP URL for connecting to Windows Chrome from WSL.

    Args:
        port: The port Chrome is listening on.

    Returns:
        Full CDP URL (e.g., "http://172.20.112.1:9222") or None.
    """
    ip = get_windows_host_ip()
    if not ip:
        return None
    return f"http://{ip}:{port}"


def _get_powershell_path() -> Path | None:
    """Find PowerShell executable on Windows side from WSL."""
    # Try PowerShell 7 (pwsh) first, then Windows PowerShell
    candidates = [
        "/mnt/c/Program Files/PowerShell/7/pwsh.exe",
        "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe",
        "/mnt/c/Windows/SysWOW64/WindowsPowerShell/v1.0/powershell.exe",
    ]
    for candidate in candidates:
        path = Path(candidate)
        if path.exists():
            return path
    return None


def check_firewall_rule(port: int = DEFAULT_WSL_CDP_PORT) -> bool:
    """Check if Windows Firewall allows inbound connections on the given port.

    Args:
        port: Port number to check.

    Returns:
        True if a rule exists, False otherwise.
    """
    if not is_wsl():
        return False

    ps_path = _get_powershell_path()
    if not ps_path:
        logger.debug("PowerShell not found")
        return False

    # Check if rule exists
    check_cmd = (
        f"Get-NetFirewallRule -DisplayName 'NotebookLM-CDP-{port}' "
        "-ErrorAction SilentlyContinue | Select-Object -First 1"
    )

    try:
        result = subprocess.run(
            [str(ps_path), "-Command", check_cmd],
            capture_output=True,
            text=True,
            errors="replace",
        )
        exists = result.returncode == 0 and result.stdout.strip()
        logger.debug(f"Firewall rule check for port {port}: {exists}")
        return exists
    except Exception as e:
        logger.warning(f"Failed to check firewall rule: {e}")
        return False


def create_firewall_rule(port: int = DEFAULT_WSL_CDP_PORT) -> tuple[bool, str]:
    """Create Windows Firewall rule to allow inbound connections from WSL.

    Args:
        port: Port number to allow.

    Returns:
        Tuple of (success: bool, message: str).
    """
    if not is_wsl():
        return False, "Not running in WSL"

    ps_path = _get_powershell_path()
    if not ps_path:
        return False, "PowerShell not found on Windows side"

    rule_name = f"NotebookLM-CDP-{port}"

    try:
        # Build PowerShell command to create firewall rule
        # This will trigger UAC prompt if not running as admin
        logger.info(f"Creating Windows Firewall rule for port {port}")

        ps_cmd = (
            f"New-NetFirewallRule -DisplayName '{rule_name}' "
            f"-Direction Inbound -Action Allow -Protocol TCP -LocalPort {port} "
            f"-RemoteAddress LocalSubnet "
            f"-Description 'Allow WSL2 to connect to Chrome DevTools Protocol for NotebookLM MCP'"
        )

        result = subprocess.run(
            [str(ps_path), "-Command", ps_cmd],
            capture_output=True,
            text=True,
        )

        if result.returncode == 0:
            msg = f"Created firewall rule '{rule_name}'"
            logger.info(msg)
            return True, msg
        else:
            error = result.stderr.strip() if result.stderr else "Unknown error"
            logger.warning(f"Failed to create firewall rule: {error}")

            # Check if it's a permission issue
            if (
                "access" in error.lower()
                or "permission" in error.lower()
                or "privilege" in error.lower()
            ):
                return False, (
                    "Administrator privileges required.\n"
                    "Please run in Windows PowerShell (as Administrator):\n"
                    f"  New-NetFirewallRule -DisplayName '{rule_name}' "
                    f"-Direction Inbound -Action Allow -Protocol TCP -LocalPort {port}"
                )
            return False, error

    except Exception as e:
        logger.error(f"Exception creating firewall rule: {e}")
        return False, str(e)


def remove_firewall_rule(port: int = DEFAULT_WSL_CDP_PORT) -> bool:
    """Remove the Windows Firewall rule for the given port.

    Args:
        port: Port number.

    Returns:
        True if successful, False otherwise.
    """
    if not is_wsl():
        return False

    ps_path = _get_powershell_path()
    if not ps_path:
        return False

    rule_name = f"NotebookLM-CDP-{port}"
    ps_cmd = f"Remove-NetFirewallRule -DisplayName '{rule_name}' -ErrorAction SilentlyContinue"

    try:
        result = subprocess.run(
            [str(ps_path), "-Command", ps_cmd],
            capture_output=True,
            text=True,
        )
        return result.returncode == 0
    except Exception as e:
        logger.warning(f"Failed to remove firewall rule: {e}")
        return False


def diagnose_wsl_connectivity(host_ip: str, port: int = DEFAULT_WSL_CDP_PORT) -> dict:
    """Run diagnostics to troubleshoot WSL->Windows connectivity issues.

    Args:
        host_ip: The Windows host IP to test.
        port: The port to test.

    Returns:
        Dictionary with diagnostic results.
    """
    results = {
        "wsl_detected": is_wsl(),
        "windows_ip": host_ip,
        "port": port,
        "tests": {},
    }

    # Test 1: Basic TCP connection
    import socket

    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)
        sock.connect((host_ip, port))
        sock.close()
        results["tests"]["tcp_connection"] = "PASS"
    except Exception as e:
        results["tests"]["tcp_connection"] = f"FAIL: {e}"

    # Test 2: HTTP request to /json
    try:
        import httpx

        response = httpx.get(f"http://{host_ip}:{port}/json", timeout=5)
        results["tests"]["http_json"] = f"Status {response.status_code}"
        if response.status_code == 200:
            data = response.json()
            results["tests"]["chrome_pages"] = len(data)
    except Exception as e:
        results["tests"]["http_json"] = f"FAIL: {e}"

    # Test 3: Check Chrome process on Windows
    ps_path = _get_powershell_path()
    if ps_path:
        try:
            ps_cmd = "Get-Process chrome -ErrorAction SilentlyContinue | Select-Object -First 1"
            result = subprocess.run(
                [str(ps_path), "-Command", ps_cmd],
                capture_output=True,
                text=True,
                timeout=10,
            )
            results["tests"]["chrome_running"] = (
                "YES" if "chrome" in result.stdout.lower() else "NO"
            )
        except Exception as e:
            results["tests"]["chrome_running"] = f"ERROR: {e}"

    # Test 4: Firewall rule
    results["tests"]["firewall_rule"] = "EXISTS" if check_firewall_rule(port) else "MISSING"

    # Test 5: Port binding on Windows
    if ps_path:
        try:
            ps_cmd = f"Get-NetTCPConnection -LocalPort {port} -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, State"
            result = subprocess.run(
                [str(ps_path), "-Command", ps_cmd],
                capture_output=True,
                text=True,
                timeout=10,
            )
            results["tests"]["port_binding"] = (
                result.stdout.strip() if result.stdout.strip() else "NOT_FOUND"
            )
        except Exception as e:
            results["tests"]["port_binding"] = f"ERROR: {e}"

    return results
