"""Ollama lifecycle management — start/stop server on demand.

Idle timeout: if we started ollama and no one marks it as active
within 60 seconds, a background thread stops it.
"""

from __future__ import annotations

import logging
import os
import shutil
import subprocess
import threading
import time
import urllib.request

logger = logging.getLogger(__name__)

_MODEL_CACHE_FILE = os.path.expanduser("~/.ai-interview-ollama-models.json")

_OLLAMA_PID_FILE = os.path.expanduser("~/.ai-interview-ollama.pid")
_OLLAMA_ACTIVE_FILE = os.path.expanduser("~/.ai-interview-ollama.active")
_IDLE_TIMEOUT = 20  # seconds
_watchdog_running = False


_DEFAULT_HOST = "http://localhost:11434"


def _is_remote(host: str) -> bool:
    """True if host points to a non-local Ollama server."""
    return host and host != _DEFAULT_HOST and "localhost" not in host and "127.0.0.1" not in host


def is_ollama_installed() -> bool:
    return bool(shutil.which("ollama"))


def is_ollama_running(host: str = _DEFAULT_HOST) -> bool:
    try:
        url = f"{host.rstrip('/')}/api/tags"
        r = subprocess.run(
            ["curl", "-sf", "--max-time", "2", url],
            capture_output=True, timeout=4,
        )
        return r.returncode == 0
    except Exception:
        return False


def we_manage_ollama() -> bool:
    """True if we started this ollama process."""
    return os.path.exists(_OLLAMA_PID_FILE)


def keep_alive():
    """Mark ollama as actively used — resets the idle timeout."""
    try:
        with open(_OLLAMA_ACTIVE_FILE, "w") as f:
            f.write(str(time.time()))
    except Exception:
        pass


def _spawn_idle_watchdog():
    """Spawn a detached process that kills ollama after idle timeout.
    Survives parent process exit."""
    global _watchdog_running
    if _watchdog_running:
        return
    _watchdog_running = True

    # Spawn a tiny Python process that sleeps and checks
    script = f"""
import os, time
PID_FILE = "{_OLLAMA_PID_FILE}"
ACTIVE_FILE = "{_OLLAMA_ACTIVE_FILE}"
TIMEOUT = {_IDLE_TIMEOUT}
while True:
    time.sleep(10)
    if not os.path.exists(PID_FILE):
        break
    try:
        ts = float(open(ACTIVE_FILE).read().strip())
    except Exception:
        ts = 0
    if time.time() - ts > TIMEOUT:
        try:
            pid = int(open(PID_FILE).read().strip())
            os.kill(pid, 15)
        except Exception:
            pass
        try: os.unlink(PID_FILE)
        except: pass
        try: os.unlink(ACTIVE_FILE)
        except: pass
        break
"""
    # Use /usr/bin/python3 — always available on macOS, no venv dependencies needed
    subprocess.Popen(
        ["/usr/bin/python3", "-c", script],
        start_new_session=True,
        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
    )


def start_ollama(host: str = _DEFAULT_HOST) -> bool:
    """Start ollama serve if not running. Returns True if running.

    For remote hosts, just checks connectivity — no local process management.
    """
    global _watchdog_running

    if _is_remote(host):
        return is_ollama_running(host)

    # If we left ollama running from a previous session and it's idle, stop it first
    if we_manage_ollama() and is_ollama_running():
        try:
            ts = float(open(_OLLAMA_ACTIVE_FILE).read().strip())
            if time.time() - ts > _IDLE_TIMEOUT:
                stop_ollama()
        except FileNotFoundError:
            stop_ollama()
        except Exception:
            pass

    if is_ollama_running():
        keep_alive()
        if not _watchdog_running and we_manage_ollama():
            _spawn_idle_watchdog()
        return True
    if not is_ollama_installed():
        return False

    env = os.environ.copy()
    env["OLLAMA_KEEP_ALIVE"] = "60s"  # unload models after 60s idle → near-zero CPU
    proc = subprocess.Popen(
        ["ollama", "serve"],
        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
        start_new_session=True,
        env=env,
    )
    try:
        with open(_OLLAMA_PID_FILE, "w") as f:
            f.write(str(proc.pid))
    except Exception:
        pass

    keep_alive()

    for _ in range(10):
        time.sleep(1)
        if is_ollama_running():
            if not _watchdog_running:
                _spawn_idle_watchdog()
            return True
    return False


def stop_ollama() -> None:
    """Stop ollama serve if we started it."""
    if not os.path.exists(_OLLAMA_PID_FILE):
        return
    try:
        pid = int(open(_OLLAMA_PID_FILE).read().strip())
        os.kill(pid, 15)
    except Exception:
        pass
    try:
        os.unlink(_OLLAMA_PID_FILE)
    except Exception:
        pass
    try:
        os.unlink(_OLLAMA_ACTIVE_FILE)
    except Exception:
        pass


def _save_model_cache(models: list[str]) -> None:
    try:
        import json
        with open(_MODEL_CACHE_FILE, "w") as f:
            json.dump(models, f)
    except Exception:
        pass


def get_cached_models() -> list[str]:
    """Return cached model list (no server needed)."""
    try:
        import json
        with open(_MODEL_CACHE_FILE) as f:
            return json.load(f)
    except Exception:
        return []


def get_installed_models(start_server: bool = False, host: str = _DEFAULT_HOST) -> list[str]:
    """Return list of installed Ollama models (local or remote).

    start_server=False: only checks if already running, falls back to cache.
    start_server=True: starts server first (with idle watchdog), updates cache.
    """
    remote = _is_remote(host)

    if not remote and not is_ollama_installed():
        return get_cached_models()
    if start_server:
        start_ollama(host)
    elif not is_ollama_running(host):
        return get_cached_models()

    # For remote hosts (or when local CLI unavailable), use the HTTP API
    if remote:
        return _get_models_via_api(host)

    try:
        r = subprocess.run(["ollama", "list"], capture_output=True, text=True, timeout=5)
        if r.returncode == 0:
            models = []
            for line in r.stdout.strip().split("\n")[1:]:
                name = line.split()[0] if line.strip() else ""
                if name:
                    models.append(name)
            _save_model_cache(models)
            return models
    except Exception:
        pass
    return get_cached_models()


def _get_models_via_api(host: str) -> list[str]:
    """Fetch model list from Ollama HTTP API (works for remote servers).

    Uses curl subprocess to bypass macOS Flet app sandbox restrictions.
    """
    try:
        import json
        url = f"{host.rstrip('/')}/api/tags"
        r = subprocess.run(
            ["curl", "-sf", "--max-time", "5", url],
            capture_output=True, text=True, timeout=8,
        )
        if r.returncode == 0 and r.stdout:
            data = json.loads(r.stdout)
            models = [m["name"] for m in data.get("models", [])]
            _save_model_cache(models)
            return models
    except Exception:
        pass
    return get_cached_models()
