"""Agent launcher command with MCP + skills bootstrap."""

from __future__ import annotations

import json
import os
import shlex
import shutil
import subprocess
import sys
import tempfile
from dataclasses import dataclass
from enum import Enum
from pathlib import Path

import click
import typer
from rich.console import Console as _RichConsole
from typer.core import TyperGroup

from worai.agent.mcp_server import run_stdio_server, start_http_server
from worai.agent.skills import wordlift_md_path, write_skill_file
from worai.config import resolve_config_path
from worai.errors import UsageError

class AgentGroup(TyperGroup):
    def invoke(self, ctx: click.Context) -> object:
        protected_args = list(ctx._protected_args or [])
        if protected_args and protected_args[0].startswith("-"):
            ctx.args = [*protected_args, *ctx.args]
            ctx._protected_args = []
        return super().invoke(ctx)


app = typer.Typer(
    add_completion=False,
    no_args_is_help=False,
    cls=AgentGroup,
    context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
)
mcp_app = typer.Typer(add_completion=False)
app.add_typer(mcp_app, name="mcp", help="Run worai MCP server utilities.")

_WORAI_SERVER_NAME = "worai"
_WORDLIFT_SERVER_NAME = "wordlift"
_DEFAULT_WORDLIFT_MCP_URL = "https://mcp.wordlift.io/sse"


class AgentCli(str, Enum):
    codex = "codex"
    claude = "claude"
    gemini = "gemini"


class McpTransport(str, Enum):
    stdio = "stdio"
    http = "http"


_WORDLIFT_ASCII_ART = (
    "██     ██  ██████  ██████  ██████  ██      ██ ███████ ████████\n"
    "██     ██ ██    ██ ██   ██ ██   ██ ██      ██ ██         ██   \n"
    "██  █  ██ ██    ██ ██████  ██   ██ ██      ██ █████      ██   \n"
    "██ ███ ██ ██    ██ ██   ██ ██   ██ ██      ██ ██         ██   \n"
    " ███ ███   ██████  ██   ██ ██████  ███████ ██ ██         ██   "
)

_BANNER_CONSOLE = _RichConsole(highlight=False)


def _print_wordlift_banner() -> None:
    _BANNER_CONSOLE.print(f"\n[bold #3452DB]{_WORDLIFT_ASCII_ART}[/]\n")
    _BANNER_CONSOLE.print("🚀 [bold]WordLift CLI[/bold] - Universal SEO Assistant")
    _BANNER_CONSOLE.rule(style="#3452DB")
    _BANNER_CONSOLE.print("🤖 [bold #3452DB]Agent WordLift[/] - SEO Specialist Mode Activated")
    _BANNER_CONSOLE.print(f"📁 [dim]Project: {Path.cwd()}[/dim]\n")


@dataclass(frozen=True)
class AgentLaunchAssets:
    temp_dir: Path
    skills_path: Path
    wordlift_md_path: Path
    mcp_config_path: Path
    gemini_home: Path


def _project_root() -> Path:
    # /.../worai/commands/agent.py -> /... (repo root for local dev)
    return Path(__file__).resolve().parents[2]


def _mcp_env() -> dict[str, str]:
    env = {"WORAI_DISABLE_UPDATE_CHECK": "1"}
    project_root = str(_project_root())
    existing_pythonpath = os.environ.get("PYTHONPATH", "").strip()
    if existing_pythonpath:
        parts = [project_root, *[part for part in existing_pythonpath.split(os.pathsep) if part]]
        deduped: list[str] = []
        for part in parts:
            if part not in deduped:
                deduped.append(part)
        env["PYTHONPATH"] = os.pathsep.join(deduped)
    else:
        env["PYTHONPATH"] = project_root
    return env


def _resolve_executable(agent_cli: AgentCli, override: str | None) -> str:
    if override and override.strip():
        return override.strip()
    return agent_cli.value


def _require_executable(name: str) -> None:
    if Path(name).is_absolute() and Path(name).exists():
        return
    if not shutil_which(name):
        raise UsageError(
            f"Executable not found: {name}. Install it or pass --agent-exec with the binary path."
        )


def shutil_which(name: str) -> str | None:
    from shutil import which

    return which(name)


def _build_mcp_command(config: str | None, profile: str | None) -> list[str]:
    cmd = [sys.executable, "-m", "worai.agent.mcp_server"]
    if config:
        cmd.extend(["--config", config])
    if profile:
        cmd.extend(["--profile", profile])
    return cmd


def _create_session_dir() -> Path:
    workspace_tmp = Path.cwd() / ".gemini" / "tmp"
    try:
        workspace_tmp.mkdir(parents=True, exist_ok=True)
        return Path(tempfile.mkdtemp(prefix="worai-agent-", dir=str(workspace_tmp)))
    except OSError:
        return Path(tempfile.mkdtemp(prefix="worai-agent-"))


def _wordlift_mcp_url() -> str:
    return os.environ.get("WORAI_WORDLIFT_MCP_URL", _DEFAULT_WORDLIFT_MCP_URL).strip()


def _wordlift_transport(url: str) -> str:
    lower = url.lower()
    if lower.endswith("/sse") or "/sse?" in lower:
        return "sse"
    return "http"


def _codex_transport(url: str) -> str:
    # Codex config supports streamable HTTP transport for URL-based servers.
    # Map SSE URLs to http to avoid stdio/url validation conflicts when an existing
    # `mcp_servers.<name>` entry is present from prior sessions.
    del url
    return "http"


def _configured_codex_mcp_servers(agent_exec: str) -> set[str]:
    completed = subprocess.run(
        [agent_exec, "mcp", "list", "--json"],
        check=False,
        capture_output=True,
        text=True,
    )
    if completed.returncode != 0:
        return set()
    try:
        payload = json.loads(completed.stdout or "[]")
    except json.JSONDecodeError:
        return set()
    if not isinstance(payload, list):
        return set()
    names: set[str] = set()
    for item in payload:
        if not isinstance(item, dict):
            continue
        name = item.get("name")
        if isinstance(name, str) and name.strip():
            names.add(name.strip())
    return names


def _gemini_env(gemini_home: Path) -> dict[str, str]:
    env = os.environ.copy()
    env["GEMINI_CLI_HOME"] = str(gemini_home)
    return env


def _setup_gemini_home(temp_dir: Path) -> Path:
    """Create an isolated GEMINI_CLI_HOME, seeding auth files from ~/.gemini/ (skipping settings)."""
    gemini_home = temp_dir / "gemini-home"
    gemini_dir = gemini_home / ".gemini"
    gemini_dir.mkdir(parents=True, exist_ok=True)
    source = Path.home() / ".gemini"
    _SKIP = {"settings.json", "tmp", "checkpoints", "history", "logs"}
    if source.is_dir():
        for item in source.iterdir():
            if item.name in _SKIP or item.name.startswith("."):
                continue
            try:
                if item.is_file():
                    shutil.copy2(item, gemini_dir / item.name)
            except OSError:
                pass
    return gemini_home


def _create_assets(
    *,
    config: str | None,
    profile: str | None,
    mcp_url: str | None = None,
) -> AgentLaunchAssets:
    temp_dir = _create_session_dir()
    gemini_home = _setup_gemini_home(temp_dir)
    gemini_dir = gemini_home / ".gemini"

    # Skills go in the default location Gemini expects: GEMINI_CLI_HOME/.gemini/skills/
    skills_dir = gemini_dir / "skills"
    skills_dir.mkdir(parents=True, exist_ok=True)
    skills_path = write_skill_file(skills_dir, project_root=Path.cwd())

    # WORDLIFT.md goes in GEMINI_CLI_HOME/.gemini/ so Gemini discovers it as a global context file.
    # contextFileName in settings tells Gemini to look for "WORDLIFT.md" by name.
    wl_md_dest = gemini_dir / "WORDLIFT.md"
    wl_md_dest.write_text(
        wordlift_md_path().read_text(encoding="utf-8")
        + "\n\n---\n\n"
        + skills_path.read_text(encoding="utf-8"),
        encoding="utf-8",
    )

    mcp_command = _build_mcp_command(config=config, profile=profile)
    mcp_config_path = temp_dir / "mcp.json"
    wordlift_url = _wordlift_mcp_url()
    if mcp_url:
        worai_server_entry: dict[str, object] = {"type": "http", "url": mcp_url}
    else:
        worai_server_entry = {
            "command": mcp_command[0],
            "args": mcp_command[1:],
            "env": _mcp_env(),
        }
    mcp_servers = {
        _WORAI_SERVER_NAME: worai_server_entry,
        _WORDLIFT_SERVER_NAME: {
            "type": _wordlift_transport(wordlift_url),
            "url": wordlift_url,
        },
    }
    (gemini_dir / "settings.json").write_text(
        json.dumps(
            {
                "ui": {"hideBanner": True},
                "mcp": {"allowed": [_WORAI_SERVER_NAME, _WORDLIFT_SERVER_NAME]},
                "mcpServers": mcp_servers,
                "experimental": {"skills": True},
                "contextFileName": "WORDLIFT.md",
                "context": {"fileName": "WORDLIFT.md"},
            },
            indent=2,
        ),
        encoding="utf-8",
    )
    # mcp.json for Claude's --mcp-config flag
    mcp_config_path.write_text(json.dumps({"mcpServers": mcp_servers}, indent=2), encoding="utf-8")
    return AgentLaunchAssets(
        temp_dir=temp_dir,
        skills_path=skills_path,
        wordlift_md_path=wl_md_dest,
        mcp_config_path=mcp_config_path,
        gemini_home=gemini_home,
    )


def _build_session_prompt(assets: AgentLaunchAssets) -> str:
    # Combined WORDLIFT.md (persona + branding + skill guidance) for Claude and Codex.
    # Gemini loads it silently via contextFileName in settings.json.
    return assets.wordlift_md_path.read_text(encoding="utf-8")


def _to_toml_inline_table(values: dict[str, str]) -> str:
    parts = [f"{key}={json.dumps(value)}" for key, value in values.items()]
    return "{" + ", ".join(parts) + "}"


def _build_launch_command(
    *,
    agent_cli: AgentCli,
    agent_exec: str,
    assets: AgentLaunchAssets,
    config: str | None,
    profile: str | None,
    transport: McpTransport,
    mcp_url: str | None,
    passthrough: list[str],
) -> tuple[list[str], dict[str, str]]:
    prompt = _build_session_prompt(assets)
    mcp_command = _build_mcp_command(config=config, profile=profile)
    if agent_cli == AgentCli.codex:
        wordlift_url = _wordlift_mcp_url()
        wordlift_transport = _codex_transport(wordlift_url)
        configured_servers = _configured_codex_mcp_servers(agent_exec)
        cmd = [agent_exec]
        if transport == McpTransport.http:
            if not mcp_url:
                raise UsageError("mcp_url is required for HTTP transport")
            if _WORAI_SERVER_NAME not in configured_servers:
                worai_table = (
                    "{"
                    f"url={json.dumps(mcp_url)}, "
                    'type="http", '
                    "enabled=true, "
                    "startup_timeout_sec=60"
                    "}"
                )
                cmd.extend(["-c", f"mcp_servers.{_WORAI_SERVER_NAME}={worai_table}"])
        else:
            mcp_env = _mcp_env()
            if _WORAI_SERVER_NAME not in configured_servers:
                worai_table = (
                    "{"
                    f"command={json.dumps(mcp_command[0])}, "
                    f"args={json.dumps(mcp_command[1:])}, "
                    f"env={_to_toml_inline_table(mcp_env)}, "
                    "enabled=true, "
                    "startup_timeout_sec=60"
                    "}"
                )
                cmd.extend(["-c", f"mcp_servers.{_WORAI_SERVER_NAME}={worai_table}"])
        if _WORDLIFT_SERVER_NAME not in configured_servers:
            cmd.extend(
                [
                    "-c",
                    (
                        f"mcp_servers.{_WORDLIFT_SERVER_NAME}="
                        "{"
                        f"url={json.dumps(wordlift_url)}, "
                        f"type={json.dumps(wordlift_transport)}, "
                        "enabled=true, "
                        "startup_timeout_sec=60"
                        "}"
                    ),
                ]
            )
        cmd.extend([*passthrough, prompt])
        return cmd, {}

    if agent_cli == AgentCli.claude:
        cmd = [
            agent_exec,
            "--mcp-config",
            str(assets.mcp_config_path),
            "--strict-mcp-config",
            "--append-system-prompt",
            prompt,
            *passthrough,
        ]
        return cmd, {}

    if agent_cli == AgentCli.gemini:
        cmd = [agent_exec, *passthrough]
        return cmd, {"WORAI_GEMINI_MCP_COMMAND": json.dumps(mcp_command)}

    raise UsageError(f"Unsupported agent CLI: {agent_cli.value}")



@app.callback(invoke_without_command=True)
def run(
    ctx: typer.Context,
    agent_cli: AgentCli = typer.Option(
        AgentCli.codex,
        "--agent-cli",
        "--cli",
        help="Agent CLI to launch: codex|claude|gemini.",
    ),
    agent_exec: str | None = typer.Option(
        None,
        "--agent-exec",
        help="Override agent CLI executable path.",
    ),
    config: str | None = typer.Option(
        None,
        "--config",
        help="Session default config path forwarded to MCP tools.",
    ),
    profile: str | None = typer.Option(
        None,
        "--profile",
        help="Session default profile forwarded to MCP tools.",
    ),
    mcp_transport: McpTransport = typer.Option(
        McpTransport.http,
        "--mcp-transport",
        help="MCP transport to expose for the agent session: stdio|http.",
    ),
    mcp_host: str = typer.Option(
        "127.0.0.1",
        "--mcp-host",
        help="Host for in-process MCP HTTP server.",
    ),
    mcp_port: int = typer.Option(
        0,
        "--mcp-port",
        help="Port for in-process MCP HTTP server (default: random free port).",
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Print the generated assets and launch command without executing.",
    ),
    keep_temp: bool = typer.Option(
        False,
        "--keep-temp",
        help="Keep generated temporary MCP/skills files after exit.",
    ),
) -> None:
    """Launch an agent CLI with worai MCP and forward extra args after `--`."""
    if ctx.invoked_subcommand:
        return

    executable = _resolve_executable(agent_cli, agent_exec)
    _require_executable(executable)

    effective_transport = mcp_transport
    if agent_cli == AgentCli.gemini and mcp_transport == McpTransport.http:
        # Gemini MCP currently interoperates with worai via stdio transport.
        effective_transport = McpTransport.stdio

    resolved_config = str(resolve_config_path(config, env=os.environ)) if config else None
    running_http = None
    mcp_url: str | None = None
    if effective_transport == McpTransport.http:
        running_http = start_http_server(
            host=mcp_host,
            port=mcp_port,
            config_path=resolved_config,
            profile=profile,
        )
        mcp_url = running_http.url

    assets = _create_assets(config=resolved_config, profile=profile, mcp_url=mcp_url)
    passthrough = list(ctx.args)
    command, extra_env = _build_launch_command(
        agent_cli=agent_cli,
        agent_exec=executable,
        assets=assets,
        config=resolved_config,
        profile=profile,
        transport=effective_transport,
        mcp_url=mcp_url,
        passthrough=passthrough,
    )

    if dry_run:
        typer.echo(f"skills: {assets.skills_path}")
        typer.echo(f"context: {assets.wordlift_md_path}")
        typer.echo(f"mcp_config: {assets.mcp_config_path}")
        typer.echo(f"launch: {shlex.join(command)}")
        if running_http is not None:
            running_http.stop()
        if not keep_temp:
            typer.echo("dry-run: temporary files retained for inspection.")
        return

    if agent_cli == AgentCli.gemini:
        _print_wordlift_banner()

    env = os.environ.copy()
    env["WORAI_DISABLE_UPDATE_CHECK"] = "1"
    env.update(extra_env)
    if agent_cli == AgentCli.gemini:
        env.update(_gemini_env(assets.gemini_home))
    try:
        completed = subprocess.run(command, env=env, check=False)
    finally:
        if running_http is not None:
            running_http.stop()
        if not keep_temp:
            for path in (assets.skills_path, assets.wordlift_md_path, assets.mcp_config_path):
                try:
                    path.unlink()
                except OSError:
                    pass
            shutil.rmtree(assets.temp_dir, ignore_errors=True)
    raise typer.Exit(code=int(completed.returncode))


@mcp_app.command("serve", help="Run worai MCP server over stdio.")
def mcp_serve(
    config: str | None = typer.Option(
        None,
        "--config",
        help="Session default config path for MCP tools.",
    ),
    profile: str | None = typer.Option(
        None,
        "--profile",
        help="Session default profile for MCP tools.",
    ),
) -> None:
    resolved_config = str(resolve_config_path(config, env=os.environ)) if config else None
    raise typer.Exit(code=run_stdio_server(config_path=resolved_config, profile=profile))
