"""System operations used by the Remote Office Center routes."""

import socket
import subprocess
import time
from datetime import datetime, timedelta, timezone
from typing import TypedDict

import psutil

from app import config
from app.schemas import AndroidDeviceRegistration, PhoneHealth, PhoneHealthUpdate
from app.wol import wake


class SystemHealth(TypedDict):
    """Desktop health information exposed by the system endpoint."""

    hostname: str
    cpu: float
    ram: float
    disk: float
    uptime: str


# The latest Android companion report. It is intentionally ephemeral until
# persistent phone-history storage is introduced in a future version.
_phone_health = PhoneHealth()


def wake_desktop() -> None:
    """Send the existing Wake-on-LAN packet to the office desktop."""
    wake(config.MAC)


def is_desktop_online() -> bool:
    """Return whether the configured desktop answers one platform-specific ping."""
    result = subprocess.run(
        [
            config.PING_COMMAND,
            config.PING_COUNT_ARGUMENT,
            "1",
            config.DESKTOP_IP,
        ],
        capture_output=True,
        text=True,
    )
    return result.returncode == 0


def connect_rustdesk() -> None:
    """Launch RustDesk using the configured desktop ID."""
    subprocess.Popen([config.RUSTDESK_EXE, "--connect", config.RUSTDESK_ID])


def open_nas_folder() -> None:
    """Open the configured NAS folder with the host's file-manager command."""
    subprocess.Popen([config.FILE_MANAGER_COMMAND, config.NAS_FOLDER])


def get_system_health() -> SystemHealth:
    """Return the local machine's current desktop health metrics."""
    uptime_seconds = max(0, int(time.time() - psutil.boot_time()))

    return {
        "hostname": socket.gethostname(),
        "cpu": psutil.cpu_percent(),
        "ram": psutil.virtual_memory().percent,
        "disk": psutil.disk_usage(config.SYSTEM_DISK_PATH).percent,
        "uptime": str(timedelta(seconds=uptime_seconds)),
    }


def get_phone_health() -> PhoneHealth:
    """Return the most recent Android companion health report."""
    return _phone_health


def _store_phone_health(updated_values: dict[str, object]) -> PhoneHealth:
    """Merge supplied Android data into the latest in-memory phone state."""
    global _phone_health

    _phone_health = _phone_health.model_copy(update=updated_values)
    return _phone_health


def update_phone_health(report: PhoneHealthUpdate) -> PhoneHealth:
    """Store submitted Android health, telemetry, and device identity values."""
    updated_values = report.model_dump(exclude_unset=True)
    # Older companions do not send a timestamp, so retain the original
    # server-side receipt time for those reports.
    if "last_seen" not in updated_values:
        updated_values["last_seen"] = datetime.now(timezone.utc)

    return _store_phone_health(updated_values)


def register_android_device(registration: AndroidDeviceRegistration) -> PhoneHealth:
    """Store Android identity details, including an optional serial number."""
    updated_values = registration.model_dump(exclude_unset=True)
    updated_values["last_seen"] = datetime.now(timezone.utc)
    return _store_phone_health(updated_values)


def _is_tailscale_device_online(status_text: str, device: str) -> bool:
    """Preserve the dashboard's original Tailscale status parsing behavior."""
    if device not in status_text:
        return False

    device_section = status_text.split(device, 1)[1][:120]
    return "offline" not in device_section


def get_tailscale_status() -> dict[str, bool] | dict[str, bool | str]:
    """Return online states from ``tailscale status`` with the existing fallback."""
    try:
        result = subprocess.run(
            [config.TAILSCALE_COMMAND, config.TAILSCALE_STATUS_ARGUMENT],
            capture_output=True,
            text=True,
        )
        status_text = result.stdout.lower()

        return {
            name: _is_tailscale_device_online(status_text, device)
            for name, device in config.TAILSCALE_DEVICES.items()
        }
    except Exception as error:
        return {
            "desktop": False,
            "phone": False,
            "nas": False,
            "laptop": False,
            "error": str(error),
        }
