"""
WiFi management via NetworkManager (nmcli).

Handles scanning, connecting, and managing WiFi connections.
"""

import subprocess
import shutil
import os
import logging
import asyncio

logger = logging.getLogger(__name__)

HOTSPOT_CON_NAME = "DuneWeaver-Hotspot"
NMCLI = shutil.which("nmcli") or "/usr/bin/nmcli"


def run_nmcli(*args: str, timeout: int = 30) -> str:
    """Run nmcli and return stdout."""
    cmd = [NMCLI] + list(args)
    logger.debug(f"Running nmcli: {' '.join(cmd)}")
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)

    if result.returncode != 0:
        logger.warning(f"nmcli failed (rc={result.returncode}): {' '.join(cmd)}")
        if result.stderr:
            logger.warning(f"  stderr: {result.stderr.strip()}")

    return result.stdout


def run_nmcli_check(*args: str, timeout: int = 30) -> subprocess.CompletedProcess:
    """Run nmcli and return the full CompletedProcess (for checking returncode)."""
    cmd = [NMCLI] + list(args)
    logger.debug(f"Running nmcli: {' '.join(cmd)}")
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)

    if result.returncode != 0:
        logger.warning(f"nmcli failed (rc={result.returncode}): {' '.join(cmd)}")
        if result.stderr:
            logger.warning(f"  stderr: {result.stderr.strip()}")

    return result


def get_wifi_mode() -> str:
    """Detect WiFi mode by querying NetworkManager active connections.

    Uses 'nmcli con show --active' instead of 'nmcli dev show wlan0'
    because the latter can behave differently in AP (hotspot) mode
    across NM versions.
    """
    try:
        output = run_nmcli("-t", "-f", "NAME,TYPE,DEVICE", "con", "show", "--active")
        logger.info(f"Active connections: {output.strip()}")
        for line in output.strip().splitlines():
            parts = line.split(":")
            if len(parts) >= 3 and parts[2] == "wlan0":
                con_name = parts[0]
                if con_name == HOTSPOT_CON_NAME:
                    return "hotspot"
                return "client"
    except (subprocess.TimeoutExpired, Exception) as e:
        logger.warning(f"Error detecting WiFi mode: {e}")
    return "unknown"


def get_current_ssid() -> str:
    """Get the SSID of the currently connected WiFi network."""
    try:
        # Use active connections to find the wifi connection on wlan0
        output = run_nmcli("-t", "-f", "NAME,TYPE,DEVICE", "con", "show", "--active")
        for line in output.strip().splitlines():
            parts = line.split(":")
            if len(parts) >= 3 and parts[2] == "wlan0":
                con_name = parts[0]
                if con_name and con_name != HOTSPOT_CON_NAME:
                    return con_name
    except (subprocess.TimeoutExpired, Exception) as e:
        logger.debug(f"Error getting SSID: {e}")
    return ""


def get_current_ip() -> str:
    """Get the current IP address of the wlan0 interface."""
    try:
        output = run_nmcli("-t", "-f", "IP4.ADDRESS", "dev", "show", "wlan0")
        logger.debug(f"IP output: {output.strip()}")
        for line in output.strip().splitlines():
            if "IP4.ADDRESS" in line:
                addr = line.split(":", 1)[1] if ":" in line else ""
                if addr:
                    return addr.split("/")[0]
    except (subprocess.TimeoutExpired, Exception) as e:
        logger.debug(f"Error getting IP: {e}")
    return ""


def get_hostname() -> str:
    """Get the system hostname via NetworkManager."""
    try:
        output = run_nmcli("general", "hostname")
        name = output.strip()
        if name:
            return name
    except Exception:
        pass
    return "duneweaver"


def get_wifi_status() -> dict:
    """Get comprehensive WiFi status."""
    mode = get_wifi_mode()
    ssid = get_current_ssid()
    ip = get_current_ip()
    hostname = get_hostname()

    return {
        "mode": mode,
        "ssid": ssid,
        "ip": ip,
        "hostname": hostname,
    }


def scan_networks() -> list[dict]:
    """Scan for available WiFi networks."""
    try:
        # Trigger rescan
        run_nmcli("dev", "wifi", "rescan", "ifname", "wlan0")
    except Exception:
        pass

    # Brief wait for scan results
    import time
    time.sleep(2)

    try:
        output = run_nmcli("-t", "-f", "SSID,SIGNAL,SECURITY,ACTIVE", "dev", "wifi", "list", "ifname", "wlan0")
    except subprocess.TimeoutExpired:
        logger.error("WiFi scan timed out")
        return []

    # Get saved connections for cross-reference
    saved = get_saved_connections()
    saved_ssids = {c["ssid"] for c in saved}

    networks = []
    seen_ssids = set()

    for line in output.strip().splitlines():
        if not line.strip():
            continue
        # nmcli -t uses : as delimiter, but SSID can contain colons
        # Format: SSID:SIGNAL:SECURITY:ACTIVE
        # Parse from the right since SSID is the only field that can contain ':'
        parts = line.rsplit(":", 3)
        if len(parts) < 4:
            continue

        ssid = parts[0].strip()
        if not ssid or ssid in seen_ssids:
            continue
        seen_ssids.add(ssid)

        try:
            signal = int(parts[1])
        except (ValueError, IndexError):
            signal = 0

        security = parts[2] if len(parts) > 2 else ""
        active = parts[3].strip().lower() == "yes" if len(parts) > 3 else False

        networks.append({
            "ssid": ssid,
            "signal": signal,
            "security": security if security and security != "--" else "Open",
            "saved": ssid in saved_ssids,
            "active": active,
        })

    # Sort by signal strength (strongest first)
    networks.sort(key=lambda n: n["signal"], reverse=True)
    return networks


def get_saved_connections() -> list[dict]:
    """Get list of saved WiFi connections."""
    try:
        output = run_nmcli("-t", "-f", "NAME,TYPE", "con", "show")
    except subprocess.TimeoutExpired:
        return []

    connections = []
    for line in output.strip().splitlines():
        if "wireless" not in line:
            continue
        name = line.split(":")[0]
        if name == HOTSPOT_CON_NAME:
            continue

        # Get the SSID for this connection
        try:
            detail = run_nmcli("-t", "-f", "802-11-wireless.ssid", "con", "show", name)
            ssid = ""
            for detail_line in detail.strip().splitlines():
                if "802-11-wireless.ssid" in detail_line:
                    ssid = detail_line.split(":", 1)[1] if ":" in detail_line else name
                    break
            if not ssid:
                ssid = name
        except Exception:
            ssid = name

        connections.append({
            "name": name,
            "ssid": ssid,
        })

    return connections


async def connect_to_network(ssid: str, password: str) -> dict:
    """Connect to a WiFi network.

    Uses explicit connection profile creation (nmcli con add) instead of
    'nmcli dev wifi connect' because the latter fails on Pi Trixie with
    'key-mgmt: property is missing' for WPA networks.
    """
    try:
        # Delete any stale connection profile for this SSID (ignore if not found)
        subprocess.run(
            [NMCLI, "con", "delete", ssid],
            capture_output=True, text=True, timeout=10,
        )

        # Create connection profile with explicit security settings
        if password:
            result = run_nmcli_check(
                "con", "add",
                "type", "wifi",
                "ifname", "wlan0",
                "con-name", ssid,
                "ssid", ssid,
                "wifi-sec.key-mgmt", "wpa-psk",
                "wifi-sec.psk", password,
                timeout=15,
            )
        else:
            result = run_nmcli_check(
                "con", "add",
                "type", "wifi",
                "ifname", "wlan0",
                "con-name", ssid,
                "ssid", ssid,
                timeout=15,
            )

        if result.returncode != 0:
            error_msg = result.stderr.strip() or "Failed to create connection"
            logger.error(f"WiFi connection add failed: {error_msg}")
            return {"success": False, "message": error_msg}

        # Activate the connection
        result = run_nmcli_check("con", "up", ssid, timeout=30)

        if result.returncode != 0:
            error_msg = result.stderr.strip() or "Failed to connect"
            logger.error(f"WiFi connect failed: {error_msg}")
            # Clean up the failed connection profile
            run_nmcli_check("con", "delete", ssid, timeout=10)
            return {"success": False, "message": error_msg}

        logger.info(f"WiFi connection to '{ssid}' successful")

        return {
            "success": True,
            "message": f"Connected to '{ssid}'.",
        }

    except subprocess.TimeoutExpired:
        return {"success": False, "message": "Connection timed out"}
    except Exception as e:
        logger.error(f"WiFi connect error: {e}")
        return {"success": False, "message": str(e)}


def save_network(ssid: str, password: str) -> dict:
    """Save a WiFi network profile without connecting.

    Creates the connection profile so autohotspot can use it on next check/boot.
    """
    try:
        # Delete any stale connection profile for this SSID (ignore if not found)
        subprocess.run(
            [NMCLI, "con", "delete", ssid],
            capture_output=True, text=True, timeout=10,
        )

        if password:
            result = run_nmcli_check(
                "con", "add",
                "type", "wifi",
                "ifname", "wlan0",
                "con-name", ssid,
                "ssid", ssid,
                "wifi-sec.key-mgmt", "wpa-psk",
                "wifi-sec.psk", password,
                timeout=15,
            )
        else:
            result = run_nmcli_check(
                "con", "add",
                "type", "wifi",
                "ifname", "wlan0",
                "con-name", ssid,
                "ssid", ssid,
                timeout=15,
            )

        if result.returncode != 0:
            error_msg = result.stderr.strip() or "Failed to save network"
            logger.error(f"WiFi save failed: {error_msg}")
            return {"success": False, "message": error_msg}

        logger.info(f"Saved WiFi network '{ssid}' (not connecting)")
        return {"success": True, "message": f"Saved '{ssid}'. Will connect automatically when in range."}

    except subprocess.TimeoutExpired:
        return {"success": False, "message": "Operation timed out"}
    except Exception as e:
        logger.error(f"WiFi save error: {e}")
        return {"success": False, "message": str(e)}


def forget_network(ssid: str) -> dict:
    """Delete a saved WiFi connection by SSID.

    If the forgotten network was the active connection, triggers the
    autohotspot script to re-evaluate and fall back to hotspot mode.
    """
    # Check if this is the currently active connection
    current_ssid = get_current_ssid()
    was_active = current_ssid == ssid or current_ssid == ssid.replace(" ", "")

    saved = get_saved_connections()
    con_name = None
    for con in saved:
        if con["ssid"] == ssid:
            con_name = con["name"]
            break

    if not con_name:
        return {"success": False, "message": f"No saved connection found for '{ssid}'"}

    try:
        result = run_nmcli_check("con", "delete", con_name, timeout=15)
        if result.returncode == 0:
            logger.info(f"Forgot WiFi network '{ssid}' (connection: {con_name})")

            # If we just forgot the active connection, re-run autohotspot
            # so it can fall back to hotspot mode
            if was_active:
                _trigger_autohotspot()

            return {"success": True, "message": f"Forgot '{ssid}'"}
        else:
            error_msg = result.stderr.strip() or "Failed to delete connection"
            return {"success": False, "message": error_msg}
    except Exception as e:
        return {"success": False, "message": str(e)}


def get_hotspot_password() -> dict:
    """Get the current hotspot password (empty string if open network)."""
    try:
        output = run_nmcli("-s", "-t", "-f", "802-11-wireless-security.psk",
                           "con", "show", HOTSPOT_CON_NAME)
        for line in output.strip().splitlines():
            if "802-11-wireless-security.psk" in line:
                psk = line.split(":", 1)[1] if ":" in line else ""
                return {"password": psk}
        return {"password": ""}
    except Exception as e:
        logger.error(f"Error getting hotspot password: {e}")
        return {"password": ""}


def set_hotspot_password(password: str) -> dict:
    """Set or remove the hotspot password.

    If password is non-empty, enables WPA-PSK. If empty, removes security
    (open network). Restarts the hotspot if it's currently active.
    """
    try:
        if password:
            if len(password) < 8:
                return {"success": False, "message": "Password must be at least 8 characters"}
            result = run_nmcli_check(
                "con", "modify", HOTSPOT_CON_NAME,
                "wifi-sec.key-mgmt", "wpa-psk",
                "wifi-sec.psk", password,
            )
        else:
            # Remove security settings entirely to make it an open network
            result = run_nmcli_check(
                "con", "modify", HOTSPOT_CON_NAME,
                "remove", "802-11-wireless-security",
            )

        if result.returncode != 0:
            error_msg = result.stderr.strip() or "Failed to update hotspot password"
            return {"success": False, "message": error_msg}

        # Restart hotspot if currently active so changes take effect
        if get_wifi_mode() == "hotspot":
            run_nmcli_check("con", "down", HOTSPOT_CON_NAME, timeout=10)
            run_nmcli_check("con", "up", HOTSPOT_CON_NAME, timeout=10)

        msg = "Hotspot password updated" if password else "Hotspot password removed (open network)"
        logger.info(msg)
        return {"success": True, "message": msg}
    except Exception as e:
        logger.error(f"Error setting hotspot password: {e}")
        return {"success": False, "message": str(e)}


def _trigger_autohotspot():
    """Trigger an immediate autohotspot check.

    Called when the active network is forgotten so the user doesn't have
    to wait for the next 60s timer tick.
    """
    autohotspot_path = "/usr/local/bin/autohotspot"
    try:
        if os.path.exists(autohotspot_path):
            logger.info("Triggering autohotspot --check after forgetting active network...")
            subprocess.Popen(
                [autohotspot_path, "--check"],
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
            )
        else:
            logger.info("Autohotspot script not found, activating hotspot directly...")
            run_nmcli("dev", "disconnect", "wlan0")
            run_nmcli("con", "up", HOTSPOT_CON_NAME)
    except Exception as e:
        logger.error(f"Failed to trigger autohotspot: {e}")
