"""Ollama backend CLI command (#16).

``sagellm ollama`` provides a set of sub-commands for interacting with
a locally-running Ollama server as an alternative backend.

Sub-commands
------------
- ``sagellm ollama status``          — check Ollama health and version
- ``sagellm ollama list``            — list available local models
- ``sagellm ollama run -p TEXT``     — generate text via Ollama
- ``sagellm ollama chat -p TEXT``    — multi-turn chat via Ollama
- ``sagellm ollama start``           — start the Ollama server process
- ``sagellm ollama stop``            — stop the managed Ollama server process
- ``sagellm ollama pull MODEL``      — pull a model from the Ollama registry

Issue
-----
#16 Implement Ollama backend
"""

from __future__ import annotations

import os
import shutil
import signal
import subprocess
import time
from pathlib import Path

import click

from sagellm.ollama_client import OllamaClient

from ..utils.console import get_console

_DEFAULT_URL = "http://localhost:11434"


@click.group()
@click.option(
    "--url",
    default=_DEFAULT_URL,
    show_default=True,
    help="Ollama server URL.",
)
@click.option(
    "--model",
    "-m",
    default="llama3",
    show_default=True,
    help="Default Ollama model.",
)
@click.pass_context
def ollama(ctx: click.Context, url: str, model: str) -> None:
    """Interact with a local Ollama server as an inference backend.

    \b
    Examples:
        sagellm ollama start
        sagellm ollama status
        sagellm ollama pull llama3
        sagellm ollama list
        sagellm ollama run -p "What is 2+2?"
        sagellm ollama chat -p "Hello!"
        sagellm ollama stop
    """
    ctx.ensure_object(dict)
    ctx.obj["ollama_url"] = url
    ctx.obj["ollama_model"] = model


@ollama.command("status")
@click.pass_context
def ollama_status(ctx: click.Context) -> None:
    """Check Ollama server health and version."""
    console = get_console()
    client = OllamaClient(
        base_url=ctx.obj["ollama_url"],
        model=ctx.obj["ollama_model"],
    )
    if client.health():
        ver = client.version()
        console.print(f"[green]Ollama is running[/green]  (version: {ver})")
        console.print(f"URL: {client.base_url}")
    else:
        console.print(
            f"[red]Ollama is NOT reachable[/red] at {client.base_url}\n"
            "Tip: start Ollama with `ollama serve`"
        )
        raise SystemExit(1)


@ollama.command("list")
@click.pass_context
def ollama_list(ctx: click.Context) -> None:
    """List locally available Ollama models."""
    console = get_console()
    client = OllamaClient(
        base_url=ctx.obj["ollama_url"],
        model=ctx.obj["ollama_model"],
    )
    models = client.list_models()
    if models:
        console.print("[bold]Locally available Ollama models:[/bold]")
        for m in models:
            console.print(f"  • {m}")
    else:
        console.print("No models found. Pull one with: ollama pull <model-name>")


@ollama.command("run")
@click.option("--prompt", "-p", required=True, help="Input prompt.")
@click.option("--max-tokens", default=128, show_default=True, help="Max output tokens.")
@click.option("--temperature", "-t", default=0.0, show_default=True, help="Sampling temperature.")
@click.pass_context
def ollama_run(ctx: click.Context, prompt: str, max_tokens: int, temperature: float) -> None:
    """Run a single text completion via Ollama.

    \b
    Examples:
        sagellm ollama run -p "Tell me a joke"
        sagellm ollama run -p "Write code" --temperature 0.7
    """
    console = get_console()
    client = OllamaClient(
        base_url=ctx.obj["ollama_url"],
        model=ctx.obj["ollama_model"],
    )
    console.print(f"[dim]Sending to Ollama ({ctx.obj['ollama_model']})...[/dim]")
    resp = client.complete(prompt, max_tokens=max_tokens, temperature=temperature)
    console.print("\n[bold]Response:[/bold]")
    console.print(resp["text"])
    usage = resp.get("usage", {})
    if usage:
        console.print(
            f"\n[dim]Tokens: prompt={usage.get('prompt_tokens', '?')} "
            f"completion={usage.get('completion_tokens', '?')}[/dim]"
        )


@ollama.command("chat")
@click.option("--prompt", "-p", required=True, help="User message.")
@click.option("--system", "-s", default=None, help="Optional system message.")
@click.option("--max-tokens", default=128, show_default=True, help="Max output tokens.")
@click.option("--temperature", "-t", default=0.7, show_default=True, help="Sampling temperature.")
@click.pass_context
def ollama_chat(
    ctx: click.Context,
    prompt: str,
    system: str | None,
    max_tokens: int,
    temperature: float,
) -> None:
    """Send a chat message to Ollama.

    \b
    Examples:
        sagellm ollama chat -p "Hello!"
        sagellm ollama chat -p "Explain Python" --system "You are a teacher."
    """
    console = get_console()
    client = OllamaClient(
        base_url=ctx.obj["ollama_url"],
        model=ctx.obj["ollama_model"],
    )
    messages: list[dict[str, str]] = []
    if system:
        messages.append({"role": "system", "content": system})
    messages.append({"role": "user", "content": prompt})

    console.print(f"[dim]Chatting with Ollama ({ctx.obj['ollama_model']})...[/dim]")
    resp = client.chat(messages, max_tokens=max_tokens, temperature=temperature)
    console.print("\n[bold]Response:[/bold]")
    console.print(resp["text"])


# ---------------------------------------------------------------------------
# Lifecycle management helpers
# ---------------------------------------------------------------------------

_PID_FILE = Path.home() / ".sagellm" / "ollama.pid"
_STARTUP_TIMEOUT = 30  # seconds to wait for Ollama to become healthy


@ollama.command("start")
@click.option(
    "--timeout",
    default=_STARTUP_TIMEOUT,
    show_default=True,
    help="Seconds to wait for Ollama to become healthy.",
)
@click.pass_context
def ollama_start(ctx: click.Context, timeout: int) -> None:
    """Start the Ollama server process in the background.

    \b
    Examples:
        sagellm ollama start
        sagellm ollama start --timeout 60
    """
    console = get_console()

    # Check if already running
    client = OllamaClient(base_url=ctx.obj["ollama_url"])
    if client.health():
        console.print("[yellow]Ollama is already running.[/yellow]")
        console.print(f"URL: {client.base_url}")
        return

    # Find the ollama binary
    ollama_bin = shutil.which("ollama")
    if ollama_bin is None:
        console.print(
            "[red]❌  'ollama' binary not found in PATH.[/red]\n"
            "Install Ollama from https://ollama.ai/download and ensure it is on your PATH."
        )
        raise SystemExit(1)

    console.print(f"[cyan]Starting Ollama server ({ollama_bin})...[/cyan]")

    # Ensure PID directory exists
    _PID_FILE.parent.mkdir(parents=True, exist_ok=True)

    # Start Ollama in the background, inheriting stdout/stderr so logs are visible
    proc = subprocess.Popen(
        [ollama_bin, "serve"],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    _PID_FILE.write_text(str(proc.pid))

    # Poll until healthy or timeout
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        time.sleep(1)
        if client.health():
            console.print(f"[green]✅  Ollama started (PID {proc.pid})[/green]")
            ver = client.version()
            console.print(f"Version: {ver}  |  URL: {client.base_url}")
            return

    # Timed out — kill the process we spawned and clean up
    proc.terminate()
    _PID_FILE.unlink(missing_ok=True)
    console.print(
        f"[red]❌  Ollama did not become healthy within {timeout}s.[/red]\n"
        "Check 'ollama serve' logs for errors."
    )
    raise SystemExit(1)


@ollama.command("stop")
def ollama_stop() -> None:
    """Stop the Ollama server process started by 'sagellm ollama start'.

    \b
    Examples:
        sagellm ollama stop
    """
    console = get_console()

    if not _PID_FILE.exists():
        console.print(
            "[yellow]No managed Ollama process found.[/yellow]\n"
            f"(PID file not present: {_PID_FILE})"
        )
        return

    pid_str = _PID_FILE.read_text().strip()
    try:
        pid = int(pid_str)
    except ValueError:
        console.print(f"[red]Invalid PID file content: {pid_str!r}[/red]")
        _PID_FILE.unlink(missing_ok=True)
        raise SystemExit(1)

    try:
        os.kill(pid, signal.SIGTERM)
        _PID_FILE.unlink(missing_ok=True)
        console.print(f"[green]✅  Ollama process (PID {pid}) stopped.[/green]")
    except ProcessLookupError:
        _PID_FILE.unlink(missing_ok=True)
        console.print(f"[yellow]Process {pid} no longer exists — cleaning up PID file.[/yellow]")
    except PermissionError:
        console.print(
            f"[red]❌  Permission denied when trying to stop PID {pid}.[/red]\n"
            "Try: kill -TERM {pid}"
        )
        raise SystemExit(1)


@ollama.command("pull")
@click.argument("model")
def ollama_pull(model: str) -> None:
    """Pull a model from the Ollama registry.

    \b
    Examples:
        sagellm ollama pull llama3
        sagellm ollama pull qwen2.5:0.5b
    """
    console = get_console()

    ollama_bin = shutil.which("ollama")
    if ollama_bin is None:
        console.print(
            "[red]❌  'ollama' binary not found in PATH.[/red]\n"
            "Install Ollama from https://ollama.ai/download"
        )
        raise SystemExit(1)

    console.print(f"[cyan]Pulling model '{model}' from Ollama registry...[/cyan]")
    result = subprocess.run([ollama_bin, "pull", model], check=False)
    if result.returncode == 0:
        console.print(f"[green]✅  Model '{model}' is ready.[/green]")
    else:
        console.print(f"[red]❌  'ollama pull {model}' failed (exit {result.returncode}).[/red]")
        raise SystemExit(result.returncode)
