"""JSON API routes."""

from __future__ import annotations

import contextlib
import json
from typing import TYPE_CHECKING, Annotated, Any

import yaml
from fastapi import APIRouter, Body, HTTPException
from fastapi.responses import HTMLResponse

if TYPE_CHECKING:
    from pathlib import Path

from compose_farm.executor import run_compose_on_host
from compose_farm.state import load_state
from compose_farm.web.deps import get_config, get_templates, reload_config

router = APIRouter(tags=["api"])


def _validate_yaml(content: str) -> None:
    """Validate YAML content, raise HTTPException on error."""
    try:
        yaml.safe_load(content)
    except yaml.YAMLError as e:
        raise HTTPException(status_code=400, detail=f"Invalid YAML: {e}") from e


def _get_service_compose_path(name: str) -> Path:
    """Get compose path for service, raising HTTPException if not found."""
    config = get_config()

    if name not in config.services:
        raise HTTPException(status_code=404, detail=f"Service '{name}' not found")

    compose_path = config.get_compose_path(name)
    if not compose_path:
        raise HTTPException(status_code=404, detail="Compose file not found")

    return compose_path


def _get_compose_services(config: Any, service: str, hosts: list[str]) -> list[dict[str, Any]]:
    """Get container info from compose file (fast, local read).

    Returns one entry per container per host for multi-host services.
    """
    compose_path = config.get_compose_path(service)
    if not compose_path or not compose_path.exists():
        return []

    compose_data = yaml.safe_load(compose_path.read_text()) or {}
    raw_services = compose_data.get("services", {})
    if not isinstance(raw_services, dict):
        return []

    # Project name is the directory name (docker compose default)
    project_name = compose_path.parent.name

    containers = []
    for host in hosts:
        for svc_name, svc_def in raw_services.items():
            # Use container_name if set, otherwise default to {project}-{service}-1
            if isinstance(svc_def, dict) and svc_def.get("container_name"):
                container_name = svc_def["container_name"]
            else:
                container_name = f"{project_name}-{svc_name}-1"
            containers.append(
                {
                    "Name": container_name,
                    "Service": svc_name,
                    "Host": host,
                    "State": "unknown",  # Status requires Docker query
                }
            )
    return containers


async def _get_container_states(
    config: Any, service: str, containers: list[dict[str, Any]]
) -> list[dict[str, Any]]:
    """Query Docker for actual container states on a single host."""
    if not containers:
        return containers

    # All containers should be on the same host
    host_name = containers[0]["Host"]

    result = await run_compose_on_host(config, service, host_name, "ps --format json", stream=False)
    if not result.success:
        return containers

    # Build state map
    state_map: dict[str, str] = {}
    for line in result.stdout.strip().split("\n"):
        if line.strip():
            with contextlib.suppress(json.JSONDecodeError):
                data = json.loads(line)
                state_map[data.get("Name", "")] = data.get("State", "unknown")

    # Update container states
    for c in containers:
        if c["Name"] in state_map:
            c["State"] = state_map[c["Name"]]

    return containers


def _render_containers(
    service: str, host: str, containers: list[dict[str, Any]], *, show_header: bool = False
) -> str:
    """Render containers HTML using Jinja template."""
    templates = get_templates()
    template = templates.env.get_template("partials/containers.html")
    module = template.make_module()
    result: str = module.host_containers(service, host, containers, show_header=show_header)
    return result


@router.get("/service/{name}/containers", response_class=HTMLResponse)
async def get_containers(name: str, host: str | None = None) -> HTMLResponse:
    """Get containers for a service as HTML buttons.

    If host is specified, queries Docker for that host's status.
    Otherwise returns all hosts with loading spinners that auto-fetch.
    """
    config = get_config()

    if name not in config.services:
        raise HTTPException(status_code=404, detail=f"Service '{name}' not found")

    # Get hosts where service is running from state
    state = load_state(config)
    current_hosts = state.get(name)
    if not current_hosts:
        return HTMLResponse('<span class="text-base-content/60">Service not running</span>')

    all_hosts = current_hosts if isinstance(current_hosts, list) else [current_hosts]

    # If host specified, return just that host's containers with status
    if host:
        if host not in all_hosts:
            return HTMLResponse(f'<span class="text-error">Host {host} not found</span>')

        containers = _get_compose_services(config, name, [host])
        containers = await _get_container_states(config, name, containers)
        return HTMLResponse(_render_containers(name, host, containers))

    # Initial load: return all hosts with loading spinners, each fetches its own status
    html_parts = []
    is_multi_host = len(all_hosts) > 1

    for h in all_hosts:
        host_id = f"containers-{name}-{h}".replace(".", "-")
        containers = _get_compose_services(config, name, [h])

        if is_multi_host:
            html_parts.append(f'<div class="font-semibold text-sm mt-3 mb-1">{h}</div>')

        # Container for this host that auto-fetches its own status
        html_parts.append(f"""
            <div id="{host_id}"
                 hx-get="/api/service/{name}/containers?host={h}"
                 hx-trigger="load"
                 hx-target="this"
                 hx-select="unset"
                 hx-swap="innerHTML">
                {_render_containers(name, h, containers)}
            </div>
        """)

    return HTMLResponse("".join(html_parts))


@router.put("/service/{name}/compose")
async def save_compose(
    name: str, content: Annotated[str, Body(media_type="text/plain")]
) -> dict[str, Any]:
    """Save compose file content."""
    compose_path = _get_service_compose_path(name)
    _validate_yaml(content)
    compose_path.write_text(content)
    return {"success": True, "message": "Compose file saved"}


@router.put("/service/{name}/env")
async def save_env(
    name: str, content: Annotated[str, Body(media_type="text/plain")]
) -> dict[str, Any]:
    """Save .env file content."""
    env_path = _get_service_compose_path(name).parent / ".env"
    env_path.write_text(content)
    return {"success": True, "message": ".env file saved"}


@router.put("/config")
async def save_config(
    content: Annotated[str, Body(media_type="text/plain")],
) -> dict[str, Any]:
    """Save compose-farm.yaml config file."""
    config = get_config()

    if not config.config_path:
        raise HTTPException(status_code=404, detail="Config path not set")

    _validate_yaml(content)
    config.config_path.write_text(content)
    reload_config()

    return {"success": True, "message": "Config saved"}
