"""ievo run — start an agent session."""

from __future__ import annotations

import subprocess
from pathlib import Path

import typer

from ievo.core.agent import AgentPackage
from ievo.core.config import AGENTS_DIR, require_project
from ievo.core.deps import ensure_mcp_configured
from ievo.core.docker import (
    DockerStatus,
    build_docker_command,
    build_sandbox_image,
    check_docker,
    image_exists,
)
from ievo.core.project import ProjectManifest
from ievo.utils.console import error, info, step, success, warn


def run(
    agent: str = typer.Argument(help="Agent name to run, e.g. spec-writer"),
    message: str | None = typer.Option(None, "-m", "--message", help="Initial message to send."),
    prompt_file: Path | None = typer.Option(None, "-p", "--prompt", help="File to feed as input."),
    model: str | None = typer.Option(None, "--model", help="Override model (opus/sonnet/haiku)."),
    local: bool = typer.Option(False, "--local", help="Run locally without Docker sandbox."),
) -> None:
    """Start an interactive agent session via Claude."""
    root = require_project()
    manifest = ProjectManifest.load(root)

    if not manifest.has_agent(agent):
        error(f"Agent [bold]{agent}[/bold] not installed. Run: ievo add {agent}")
        raise typer.Exit(1)

    agent_dir = root / AGENTS_DIR / agent

    try:
        pkg = AgentPackage.load(agent_dir)
    except FileNotFoundError:
        error(f"agent.yaml not found in {agent_dir}")
        raise typer.Exit(1) from None

    # Determine model
    agent_model = model or pkg.model.primary

    # Check for overrides in project manifest
    if not model and agent in manifest.overrides:
        override_model = manifest.overrides[agent].get("model", {}).get("primary")
        if override_model:
            agent_model = override_model

    # Ensure MCP deps are configured
    if pkg.mcp:
        mcp_warnings = ensure_mcp_configured(root, pkg.mcp)
        for w in mcp_warnings:
            warn(w)

    # Warn about plugin deps
    for plugin in pkg.plugins:
        if plugin.required:
            warn(
                f"Plugin '{plugin.name}' ({plugin.marketplace}) required. "
                f"Ensure installed: /plugin install {plugin.name}"
            )

    info(f"Starting [bold]{agent}[/bold] session...")
    step(f"Model: {agent_model}")
    step(f"Memory: {agent_dir / 'memory'}")

    # Decide execution mode: Docker (default) or local
    if local:
        _run_local(root, agent_dir, pkg, agent_model, message, prompt_file)
    else:
        _run_docker(root, agent_dir, pkg, agent_model, message, prompt_file)


def _run_docker(
    root: Path,
    agent_dir: Path,
    pkg: AgentPackage,
    model: str,
    message: str | None,
    prompt_file: Path | None,
) -> None:
    """Run agent in Docker sandbox."""
    docker_check = check_docker()

    if docker_check.status == DockerStatus.NOT_INSTALLED:
        warn("Docker not found. Falling back to local execution.")
        _run_local(root, agent_dir, pkg, model, message, prompt_file)
        return

    if docker_check.status == DockerStatus.NOT_RUNNING:
        warn("Docker daemon not running. Falling back to local execution.")
        _run_local(root, agent_dir, pkg, model, message, prompt_file)
        return

    # Check if sandbox image exists
    if not image_exists():
        info("Sandbox image not found. Building...")
        sandbox_dir = _find_sandbox_dir(root)
        if sandbox_dir is None:
            warn("sandbox/Dockerfile not found. Falling back to local execution.")
            _run_local(root, agent_dir, pkg, model, message, prompt_file)
            return

        if not build_sandbox_image(sandbox_dir):
            error("Failed to build sandbox image.")
            warn("Falling back to local execution.")
            _run_local(root, agent_dir, pkg, model, message, prompt_file)
            return

        success("Sandbox image built.")

    step("Mode: Docker sandbox")
    cmd = build_docker_command(root, agent_dir, pkg, model, message, prompt_file)

    try:
        subprocess.run(cmd, cwd=str(root), check=False)
    except FileNotFoundError:
        error("Docker CLI not found.")
        raise typer.Exit(1) from None
    except KeyboardInterrupt:
        success("Session ended.")


def _run_local(
    root: Path,
    agent_dir: Path,
    pkg: AgentPackage,
    model: str,
    message: str | None,
    prompt_file: Path | None,
) -> None:
    """Run agent locally via Claude CLI subprocess."""
    step("Mode: local")
    cmd = _build_claude_command(root, agent_dir, pkg, model, message, prompt_file)

    info("Launching Claude...")

    try:
        subprocess.run(cmd, cwd=str(root), check=False)
    except FileNotFoundError:
        error("Claude CLI not found. Install: https://docs.claude.com/en/docs/claude-code")
        raise typer.Exit(1) from None
    except KeyboardInterrupt:
        success("Session ended.")


def _find_sandbox_dir(root: Path) -> Path | None:
    """Find sandbox/Dockerfile relative to project or ievo package."""
    # Check in project root
    project_sandbox = root / "sandbox"
    if (project_sandbox / "Dockerfile").exists():
        return project_sandbox

    # Check relative to ievo package (cli repo)
    pkg_sandbox = Path(__file__).resolve().parent.parent.parent.parent / "sandbox"
    if (pkg_sandbox / "Dockerfile").exists():
        return pkg_sandbox

    return None


def _build_claude_command(
    root: Path,
    agent_dir: Path,
    pkg: AgentPackage,
    model: str,
    message: str | None,
    prompt_file: Path | None,
) -> list[str]:
    """Build the claude CLI command."""
    cmd = ["claude"]

    # Model mapping
    model_map = {
        "opus": "opus",
        "sonnet": "sonnet",
        "haiku": "haiku",
    }
    if model in model_map:
        cmd.extend(["--model", model_map[model]])

    # System prompt from ROLE.md
    role_md = agent_dir / "ROLE.md"
    if role_md.exists():
        cmd.extend(["--system-prompt", role_md.read_text()])

    # Append memory context
    memory_dir = agent_dir / "memory"
    if memory_dir.exists():
        memory_context = []
        for mf in sorted(memory_dir.glob("*.md")):
            content = mf.read_text().strip()
            if content and content != f"# {mf.stem}":
                memory_context.append(f"## {mf.stem}\n\n{content}")
        if memory_context:
            cmd.extend(["--append-system-prompt", "\n\n---\n\n".join(memory_context)])

    # Initial message
    if prompt_file:
        cmd.extend(["-p", prompt_file.read_text()])
    elif message:
        cmd.extend(["-p", message])

    return cmd
