"""
Mac Mini Remote Control API.

Provides endpoints for:
- Power control (shutdown, restart, wake-on-LAN)
- Status monitoring (ping, SSH, services)
- Docker container management
- Ollama model management

This API can run in two modes:
1. Remote mode: Running on MacBook, controlling Mac Mini via SSH
2. Local mode: Running on Mac Mini (in Docker), using direct commands
"""

import asyncio
import subprocess
import os
import httpx
from typing import Optional, List, Dict, Any
from fastapi import APIRouter, HTTPException, BackgroundTasks
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import json
import socket

router = APIRouter(prefix="/api/mac-mini", tags=["Mac Mini Control"])

# Configuration
MAC_MINI_IP = os.getenv("MAC_MINI_IP", "192.168.178.100")
MAC_MINI_USER = os.getenv("MAC_MINI_USER", "benjaminadmin")
MAC_MINI_MAC = os.getenv("MAC_MINI_MAC", "")  # MAC address for Wake-on-LAN
PROJECT_PATH = "/Users/benjaminadmin/Projekte/breakpilot-pwa"

# Detect if running inside Docker (local mode on Mac Mini)
# In Docker, we use host.docker.internal to access host services
RUNNING_IN_DOCKER = os.path.exists("/.dockerenv")
DOCKER_HOST_IP = "host.docker.internal" if RUNNING_IN_DOCKER else MAC_MINI_IP
OLLAMA_HOST = f"http://{DOCKER_HOST_IP}:11434" if RUNNING_IN_DOCKER else f"http://{MAC_MINI_IP}:11434"


class ModelPullRequest(BaseModel):
    model: str


class CommandResponse(BaseModel):
    success: bool
    message: str
    output: Optional[str] = None


async def run_ssh_command(command: str, timeout: int = 30) -> tuple[bool, str]:
    """Run a command via SSH on Mac Mini."""
    ssh_cmd = f"ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=no {MAC_MINI_USER}@{MAC_MINI_IP} \"{command}\""
    try:
        process = await asyncio.create_subprocess_shell(
            ssh_cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        stdout, stderr = await asyncio.wait_for(
            process.communicate(),
            timeout=timeout
        )
        output = stdout.decode() + stderr.decode()
        return process.returncode == 0, output.strip()
    except asyncio.TimeoutError:
        return False, "Command timed out"
    except Exception as e:
        return False, str(e)


async def check_ping() -> bool:
    """Check if Mac Mini responds to ping."""
    try:
        process = await asyncio.create_subprocess_shell(
            f"ping -c 1 -W 2 {MAC_MINI_IP}",
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.DEVNULL
        )
        await process.wait()
        return process.returncode == 0
    except:
        return False


async def check_ssh() -> bool:
    """Check if SSH is available."""
    success, _ = await run_ssh_command("echo ok", timeout=10)
    return success


async def check_service_http(url: str, timeout: int = 5) -> bool:
    """Check if an HTTP service is responding."""
    try:
        async with httpx.AsyncClient(timeout=timeout) as client:
            response = await client.get(url)
            return response.status_code == 200
    except:
        return False


async def check_internet() -> bool:
    """Check if Mac Mini has internet access by pinging external servers."""
    # Try multiple methods for reliability

    # Method 1: HTTP check to a reliable endpoint
    try:
        async with httpx.AsyncClient(timeout=5) as client:
            response = await client.get("https://www.google.com/generate_204")
            if response.status_code == 204:
                return True
    except:
        pass

    # Method 2: DNS resolution check
    try:
        socket.getaddrinfo("google.com", 80, socket.AF_INET)
        return True
    except:
        pass

    # Method 3: Ping to 8.8.8.8 (Google DNS)
    try:
        process = await asyncio.create_subprocess_shell(
            "ping -c 1 -W 2 8.8.8.8",
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.DEVNULL
        )
        await asyncio.wait_for(process.wait(), timeout=5)
        if process.returncode == 0:
            return True
    except:
        pass

    return False


async def get_local_system_info():
    """Get system info when running locally (in Docker on Mac Mini)."""
    uptime = None
    cpu_load = None
    memory = None

    # These won't work inside Docker, but we can try basic checks
    # The host system info requires the Docker socket or host access

    return uptime, cpu_load, memory


async def get_docker_containers_local():
    """Get Docker container status when running inside Docker via Docker socket."""
    containers = []

    docker_socket = "/var/run/docker.sock"
    if not os.path.exists(docker_socket):
        return False, containers

    try:
        # Use Python to query Docker API via Unix socket (no curl needed)
        import urllib.request
        import urllib.error

        class UnixSocketHandler(urllib.request.AbstractHTTPHandler):
            def unix_open(self, req):
                import http.client
                import socket as sock

                class UnixHTTPConnection(http.client.HTTPConnection):
                    def __init__(self, socket_path):
                        super().__init__("localhost")
                        self.socket_path = socket_path

                    def connect(self):
                        self.sock = sock.socket(sock.AF_UNIX, sock.SOCK_STREAM)
                        self.sock.connect(self.socket_path)

                conn = UnixHTTPConnection(docker_socket)
                conn.request(req.get_method(), req.selector)
                return conn.getresponse()

        # Query Docker API
        opener = urllib.request.build_opener(UnixSocketHandler())
        req = urllib.request.Request("unix:///containers/json?all=true")
        response = opener.open(req, timeout=5)
        data = json.loads(response.read().decode())

        for c in data:
            name = c.get("Names", ["/unknown"])[0].lstrip("/")
            state = c.get("State", "unknown")
            status = c.get("Status", state)
            containers.append({"name": name, "status": status})
        return True, containers
    except Exception as e:
        # Fallback: try using httpx with unix socket support
        try:
            import httpx
            transport = httpx.HTTPTransport(uds=docker_socket)
            async with httpx.AsyncClient(transport=transport, timeout=5) as client:
                response = await client.get("http://localhost/containers/json?all=true")
                if response.status_code == 200:
                    data = response.json()
                    for c in data:
                        name = c.get("Names", ["/unknown"])[0].lstrip("/")
                        state = c.get("State", "unknown")
                        status = c.get("Status", state)
                        containers.append({"name": name, "status": status})
                    return True, containers
        except:
            pass

    return False, containers


@router.get("/status")
async def get_status():
    """Get comprehensive Mac Mini status."""

    # When running inside Docker on Mac Mini, we're always "online"
    if RUNNING_IN_DOCKER:
        # Local mode - running on Mac Mini itself
        # Check services in parallel
        ollama_task = asyncio.create_task(check_service_http(f"{OLLAMA_HOST}/api/tags"))
        internet_task = asyncio.create_task(check_internet())
        docker_task = asyncio.create_task(get_docker_containers_local())

        ollama_ok = await ollama_task
        internet_ok = await internet_task
        docker_ok, containers = await docker_task

        # Get Ollama models
        models = []
        if ollama_ok:
            try:
                async with httpx.AsyncClient(timeout=10) as client:
                    response = await client.get(f"{OLLAMA_HOST}/api/tags")
                    if response.status_code == 200:
                        data = response.json()
                        models = data.get("models", [])
            except:
                pass

        return {
            "online": True,
            "ip": MAC_MINI_IP,
            "ping": True,
            "ssh": True,  # We're running locally, SSH not needed
            "docker": docker_ok,
            "backend": True,  # We're the backend
            "ollama": ollama_ok,
            "internet": internet_ok,  # Neuer Status: Internet-Zugang
            "uptime": "Lokal",  # Can't get this from inside Docker easily
            "cpu_load": None,
            "memory": None,
            "containers": containers,
            "models": models
        }

    # Remote mode - running on MacBook, checking Mac Mini via SSH
    # Start all checks in parallel
    ping_task = asyncio.create_task(check_ping())

    # Wait for ping first
    ping_ok = await ping_task

    if not ping_ok:
        return {
            "online": False,
            "ip": MAC_MINI_IP,
            "ping": False,
            "ssh": False,
            "docker": False,
            "backend": False,
            "ollama": False,
            "internet": False,
            "uptime": None,
            "cpu_load": None,
            "memory": None,
            "containers": [],
            "models": []
        }

    # If ping succeeds, check other services
    ssh_task = asyncio.create_task(check_ssh())
    backend_task = asyncio.create_task(check_service_http(f"http://{MAC_MINI_IP}:8000/health"))
    ollama_task = asyncio.create_task(check_service_http(f"http://{MAC_MINI_IP}:11434/api/tags"))

    ssh_ok = await ssh_task
    backend_ok = await backend_task
    ollama_ok = await ollama_task

    # Check internet via SSH on Mac Mini
    internet_ok = False

    # Get system info if SSH is available
    uptime = None
    cpu_load = None
    memory = None
    docker_ok = False
    containers = []
    models = []

    if ssh_ok:
        # Get uptime
        success, output = await run_ssh_command("uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}'")
        if success:
            uptime = output.strip()

        # Get CPU load
        success, output = await run_ssh_command("sysctl -n vm.loadavg | awk '{print $2}'")
        if success:
            cpu_load = output.strip()

        # Get memory usage
        success, output = await run_ssh_command("vm_stat | awk '/Pages active/ {active=$3} /Pages wired/ {wired=$3} END {print int((active+wired)*4096/1024/1024/1024*10)/10 \" GB\"}'")
        if success:
            memory = output.strip()

        # Check Docker and get containers
        success, output = await run_ssh_command("/usr/local/bin/docker ps --format '{{.Names}}|{{.Status}}'")
        if success:
            docker_ok = True
            for line in output.strip().split('\n'):
                if '|' in line:
                    name, status = line.split('|', 1)
                    containers.append({"name": name, "status": status})

        # Check internet access on Mac Mini
        success, _ = await run_ssh_command("ping -c 1 -W 2 8.8.8.8", timeout=10)
        internet_ok = success

    # Get Ollama models if available
    if ollama_ok:
        try:
            async with httpx.AsyncClient(timeout=10) as client:
                response = await client.get(f"http://{MAC_MINI_IP}:11434/api/tags")
                if response.status_code == 200:
                    data = response.json()
                    models = data.get("models", [])
        except:
            pass

    return {
        "online": ping_ok and ssh_ok,
        "ip": MAC_MINI_IP,
        "ping": ping_ok,
        "ssh": ssh_ok,
        "docker": docker_ok,
        "backend": backend_ok,
        "ollama": ollama_ok,
        "internet": internet_ok,
        "uptime": uptime,
        "cpu_load": cpu_load,
        "memory": memory,
        "containers": containers,
        "models": models
    }


@router.post("/wake")
async def wake_on_lan():
    """Send Wake-on-LAN magic packet to Mac Mini."""
    if not MAC_MINI_MAC:
        # Try to get MAC address from ARP cache
        try:
            process = await asyncio.create_subprocess_shell(
                f"arp -n {MAC_MINI_IP} | awk '{{print $3}}'",
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, _ = await process.communicate()
            mac = stdout.decode().strip()
            if not mac or mac == "(incomplete)":
                return CommandResponse(
                    success=False,
                    message="MAC-Adresse nicht gefunden. Bitte MAC_MINI_MAC setzen.",
                    output="Wake-on-LAN benötigt die MAC-Adresse des Mac Mini"
                )
        except:
            return CommandResponse(
                success=False,
                message="Fehler beim Ermitteln der MAC-Adresse"
            )
    else:
        mac = MAC_MINI_MAC

    # Send WOL packet using wakeonlan if available, otherwise use Python
    try:
        # Try wakeonlan command first
        process = await asyncio.create_subprocess_shell(
            f"wakeonlan {mac}",
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE
        )
        stdout, stderr = await process.communicate()

        if process.returncode == 0:
            return CommandResponse(
                success=True,
                message=f"Wake-on-LAN Paket an {mac} gesendet. Der Mac Mini sollte in 30-60 Sekunden starten.",
                output=stdout.decode()
            )
        else:
            # Fall back to Python WOL
            import socket
            import struct

            # Create magic packet
            mac_bytes = bytes.fromhex(mac.replace(':', '').replace('-', ''))
            magic = b'\xff' * 6 + mac_bytes * 16

            # Send to broadcast
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            sock.sendto(magic, ('255.255.255.255', 9))
            sock.close()

            return CommandResponse(
                success=True,
                message=f"Wake-on-LAN Paket an {mac} gesendet (Python fallback).",
                output="Magic packet sent via broadcast"
            )
    except Exception as e:
        return CommandResponse(
            success=False,
            message=f"Fehler beim Senden des WOL-Pakets: {str(e)}"
        )


@router.post("/restart")
async def restart_mac_mini():
    """Restart Mac Mini via SSH."""
    # Use osascript for graceful restart
    success, output = await run_ssh_command(
        "osascript -e 'tell application \"System Events\" to restart'",
        timeout=15
    )

    if success:
        return CommandResponse(
            success=True,
            message="Neustart wurde ausgelöst. Der Mac Mini startet in wenigen Sekunden neu.",
            output=output
        )
    else:
        # Try sudo reboot as fallback
        success, output = await run_ssh_command("sudo -n reboot", timeout=15)
        if success:
            return CommandResponse(
                success=True,
                message="Neustart wurde ausgelöst (sudo).",
                output=output
            )
        return CommandResponse(
            success=False,
            message="Neustart fehlgeschlagen. SSH-Verbindung oder Berechtigung fehlt.",
            output=output
        )


@router.post("/shutdown")
async def shutdown_mac_mini():
    """Shutdown Mac Mini via SSH."""
    # Use osascript for graceful shutdown
    success, output = await run_ssh_command(
        "osascript -e 'tell application \"System Events\" to shut down'",
        timeout=15
    )

    if success:
        return CommandResponse(
            success=True,
            message="Shutdown wurde ausgelöst. Der Mac Mini fährt in wenigen Sekunden herunter.",
            output=output
        )
    else:
        return CommandResponse(
            success=False,
            message="Shutdown fehlgeschlagen. SSH-Verbindung oder Berechtigung fehlt.",
            output=output
        )


@router.post("/docker/up")
async def docker_up():
    """Start Docker containers on Mac Mini."""
    success, output = await run_ssh_command(
        f"cd {PROJECT_PATH} && /usr/local/bin/docker compose up -d",
        timeout=120
    )

    return CommandResponse(
        success=success,
        message="Container werden gestartet..." if success else "Fehler beim Starten der Container",
        output=output
    )


@router.post("/docker/down")
async def docker_down():
    """Stop Docker containers on Mac Mini."""
    success, output = await run_ssh_command(
        f"cd {PROJECT_PATH} && /usr/local/bin/docker compose down",
        timeout=60
    )

    return CommandResponse(
        success=success,
        message="Container werden gestoppt..." if success else "Fehler beim Stoppen der Container",
        output=output
    )


@router.post("/ollama/pull")
async def pull_ollama_model(request: ModelPullRequest):
    """Pull an Ollama model with streaming progress."""
    model_name = request.model

    async def stream_progress():
        """Stream the pull progress as JSON lines."""
        try:
            async with httpx.AsyncClient(timeout=None) as client:
                async with client.stream(
                    "POST",
                    f"{OLLAMA_HOST}/api/pull",
                    json={"name": model_name, "stream": True}
                ) as response:
                    async for line in response.aiter_lines():
                        if line:
                            yield line + "\n"
        except Exception as e:
            yield json.dumps({"status": f"error: {str(e)}"}) + "\n"

    return StreamingResponse(
        stream_progress(),
        media_type="application/x-ndjson"
    )


@router.get("/ollama/models")
async def get_ollama_models():
    """Get list of installed Ollama models."""
    try:
        async with httpx.AsyncClient(timeout=10) as client:
            response = await client.get(f"{OLLAMA_HOST}/api/tags")
            if response.status_code == 200:
                return response.json()
            return {"models": []}
    except:
        return {"models": []}


@router.delete("/ollama/models/{model_name}")
async def delete_ollama_model(model_name: str):
    """Delete an Ollama model."""
    try:
        async with httpx.AsyncClient(timeout=30) as client:
            response = await client.delete(
                f"{OLLAMA_HOST}/api/delete",
                json={"name": model_name}
            )
            if response.status_code == 200:
                return CommandResponse(
                    success=True,
                    message=f"Modell '{model_name}' wurde gelöscht"
                )
            return CommandResponse(
                success=False,
                message=f"Fehler beim Löschen: {response.text}"
            )
    except Exception as e:
        return CommandResponse(
            success=False,
            message=f"Fehler: {str(e)}"
        )
