"""Chat command — interactive terminal chat with an Agent.

Runs a lightweight in-process server and opens a REPL-style chat session.
No external server or Docker needed.

Usage:
    agentkit chat              # Start chatting (auto-onboard if no config)
    agentkit chat --model deepseek/deepseek-chat  # Use specific model
"""

from __future__ import annotations

import asyncio
import os
from typing import Any

import typer
from rich import print as rprint
from rich.panel import Panel
from rich.prompt import Prompt
from rich.markdown import Markdown
from rich.live import Live
from rich.text import Text
from rich.console import Group


def chat(
    model: str = typer.Option("default", "--model", "-m", help="LLM model to use (e.g. deepseek/deepseek-chat)"),
    agent_name: str = typer.Option("default", "--agent", "-a", help="Agent name to chat with"),
    config: str | None = typer.Option(None, "--config", "-c", help="Path to agentkit.yaml"),
    system_prompt: str | None = typer.Option(None, "--system-prompt", "-s", help="Custom system prompt"),
    no_stream: bool = typer.Option(False, "--no-stream", help="Disable token streaming"),
):
    """Start an interactive chat session with an Agent."""
    asyncio.run(_chat_async(model, agent_name, config, system_prompt, no_stream))


async def _chat_async(
    model: str,
    agent_name: str,
    config_arg: str | None,
    system_prompt: str | None,
    no_stream: bool,
) -> None:
    """Async implementation of the chat command."""
    from agentkit.cli.onboarding import run_onboarding
    from agentkit.server.config import ServerConfig, find_config_path

    # ── Onboarding check ──────────────────────────────────────────
    config_path = find_config_path(config_arg)
    if config_path is None:
        config_path = run_onboarding(config_arg=config_arg)
        if config_path is None:
            rprint("[red]Onboarding cancelled. Cannot start chat without configuration.[/red]")
            raise typer.Exit(code=1)

    # ── Load config ───────────────────────────────────────────────
    rprint(f"[dim]Loading config from {config_path}[/dim]")

    # Load .env
    from pathlib import Path
    dotenv = Path(config_path).parent / ".env"
    if dotenv.exists():
        _load_dotenv(str(dotenv))

    server_config = ServerConfig.from_yaml(config_path)

    # ── Build in-process components ───────────────────────────────
    from agentkit.session.manager import SessionManager
    from agentkit.session.store import InMemorySessionStore
    from agentkit.session.models import MessageRole
    from agentkit.core.react import ReActEngine
    from agentkit.tools.base import Tool
    from agentkit.memory.profile import MemoryStore
    from agentkit.tools.memory_tool import MemoryTool
    from agentkit.tools.shell import ShellTool
    from agentkit.tools.web_search import WebSearchTool
    from agentkit.tools.web_crawl import WebCrawlTool

    # Build LLM Gateway
    gateway = _build_gateway(server_config)

    # Initialize memory store
    memory_store = MemoryStore()
    memory_store.ensure_defaults()
    memory_snapshot = memory_store.load_all()

    # Create session
    session_manager = SessionManager(store=InMemorySessionStore())
    session = await session_manager.create_session(agent_name=agent_name)

    # Build tools list — all available tools for chat mode
    search_api_keys = _extract_search_keys(server_config)
    tools: list[Tool] = [
        MemoryTool(memory_store=memory_store),
        ShellTool(working_dir=os.getcwd()),
        WebSearchTool(**search_api_keys),
        WebCrawlTool(),
    ]

    # ── Load skills and build IntentRouter ───────────────────────
    from agentkit.tools.registry import ToolRegistry
    from agentkit.skills.registry import SkillRegistry
    from agentkit.skills.loader import SkillLoader
    from agentkit.router.intent import IntentRouter

    tool_registry = ToolRegistry()
    for tool in tools:
        tool_registry.register(tool)

    skill_registry = SkillRegistry()
    if server_config.skill_paths:
        loader = SkillLoader(skill_registry=skill_registry, tool_registry=tool_registry)
        for skill_path in server_config.skill_paths:
            from pathlib import Path as _P
            p = _P(skill_path)
            if p.is_dir():
                loaded = loader.load_from_directory(str(p))
                if loaded:
                    rprint(f"[dim]Loaded {len(loaded)} skills from {p}[/dim]")
            elif p.is_file() and p.suffix in (".yaml", ".yml"):
                try:
                    loader.load_from_file(str(p))
                except Exception:
                    pass

    intent_router = IntentRouter(llm_gateway=gateway) if skill_registry.list_skills() else None

    # Build system prompt — inject memory into system prompt
    base_prompt = system_prompt or (
        "你是一个有帮助的AI助手。请记住我们对话的上下文，并在后续对话中引用之前的内容。回答要清晰简洁，请使用中文回复。"
    )
    effective_system_prompt = memory_store.build_system_prompt(memory_snapshot, base_prompt)

    # Resolve agent display name from SOUL.md
    agent_display_name = memory_store.get_file("soul").read_section("身份") or agent_name
    # Extract just the name (first line after "我是")
    for prefix in ["我是", "我叫", "我的名字是"]:
        if prefix in agent_display_name:
            name_part = agent_display_name.split(prefix, 1)[1].strip()
            # Take first meaningful token (before comma, period, etc.)
            for sep in ["，", "。", "、", ",", ".", " "]:
                if sep in name_part:
                    name_part = name_part.split(sep)[0]
                    break
            agent_display_name = name_part
            break

    # ── Welcome banner ────────────────────────────────────────────
    effective_model = model if model != "default" else _resolve_default_model(server_config)
    rprint(Panel(
        f"[bold]AgentKit Chat[/bold]\n\n"
        f"  Model: [cyan]{effective_model}[/cyan]\n"
        f"  Agent: [cyan]{agent_display_name}[/cyan]\n"
        f"  Session: [dim]{session.session_id[:8]}...[/dim]\n\n"
        f"  Type your message and press Enter.\n"
        f"  [dim]/help[/dim] — Show commands\n"
        f"  [dim]/clear[/dim] — Clear conversation\n"
        f"  [dim]/model <name>[/dim] — Switch model\n"
        f"  [dim]/quit[/dim] — Exit chat",
        title="AgentKit",
        border_style="bright_blue",
    ))

    # ── Chat loop ─────────────────────────────────────────────────
    react_engine = ReActEngine(llm_gateway=gateway)
    current_model = effective_model
    conversation_had_messages = False

    while True:
        try:
            user_input = Prompt.ask("\n[bold green]You[/bold green]")
        except (EOFError, KeyboardInterrupt):
            rprint("\n[dim]Goodbye![/dim]")
            break

        if not user_input.strip():
            continue

        # Handle commands
        if user_input.startswith("/"):
            cmd = user_input.strip().lower()
            if cmd in ("/quit", "/q", "/exit"):
                rprint("[dim]Goodbye![/dim]")
                break
            elif cmd == "/help":
                _print_help()
                continue
            elif cmd == "/clear":
                # Create a new session (memory files persist)
                session = await session_manager.create_session(agent_name=agent_name)
                rprint("[dim]Conversation cleared. New session started.[/dim]")
                continue
            elif cmd.startswith("/model "):
                current_model = cmd.split(" ", 1)[1].strip()
                rprint(f"[dim]Switched to model: {current_model}[/dim]")
                continue
            else:
                rprint(f"[yellow]Unknown command: {cmd}[/yellow]")
                continue

        conversation_had_messages = True

        # Append user message to session
        await session_manager.append_message(
            session_id=session.session_id,
            role=MessageRole.USER,
            content=user_input,
        )

        # Get full conversation history (includes all previous turns)
        chat_messages = await session_manager.get_chat_messages(session.session_id)

        # ── Skill routing ─────────────────────────────────────────
        from agentkit.chat.skill_routing import resolve_skill_routing

        routing = await resolve_skill_routing(
            content=user_input,
            skill_registry=skill_registry,
            intent_router=intent_router,
            default_tools=tools,
            default_system_prompt=effective_system_prompt,
            default_model=current_model,
            default_agent_name=agent_name,
            session_id=session.session_id,
        )

        if routing.matched:
            rprint(f"[dim]Skill: {routing.skill_name} ({routing.match_method}, {int(routing.match_confidence * 100)}%)[/dim]")

        exec_system_prompt = routing.system_prompt
        exec_tools = routing.tools
        exec_model = routing.model

        # Print Agent label before streaming
        rprint(f"\n[bold blue]{agent_display_name}[/bold blue]: ", end="")

        # Execute Agent
        try:
            if no_stream:
                # Non-streaming mode
                result = await react_engine.execute(
                    messages=chat_messages,
                    tools=exec_tools,
                    model=exec_model,
                    agent_name=routing.skill_name or agent_name,
                    system_prompt=exec_system_prompt,
                )
                output = result.output if hasattr(result, "output") else str(result)
                rprint(output)

                await session_manager.append_message(
                    session_id=session.session_id,
                    role=MessageRole.ASSISTANT,
                    content=output,
                    agent_name=agent_name,
                )
            else:
                # Streaming mode — Live displays under the "Agent:" label
                full_content = ""
                with Live(
                    Text(""),
                    refresh_per_second=15,
                    vertical_overflow="visible",
                    transient=False,  # Keep final output on screen
                ) as live:
                    async for event in react_engine.execute_stream(
                        messages=chat_messages,
                        tools=exec_tools,
                        model=exec_model,
                        agent_name=routing.skill_name or agent_name,
                        system_prompt=exec_system_prompt,
                    ):
                        if event.event_type == "token":
                            token = event.data.get("content", "")
                            full_content += token
                            live.update(Text(full_content))
                        elif event.event_type == "final_answer":
                            # Use final_answer output (may differ slightly from accumulated tokens)
                            full_content = event.data.get("output", full_content)
                            live.update(Markdown(full_content))
                        elif event.event_type == "tool_call":
                            tool_name = event.data.get("tool_name", "unknown")
                            live.update(Text(f"[calling tool: {tool_name}...]"))
                        elif event.event_type == "tool_result":
                            # After tool result, show accumulated content again
                            if full_content:
                                live.update(Text(full_content))

                # Live already displayed the final content, no need to rprint again

                await session_manager.append_message(
                    session_id=session.session_id,
                    role=MessageRole.ASSISTANT,
                    content=full_content,
                    agent_name=agent_name,
                )

        except Exception as e:
            rprint(f"\n[red]Error: {e}[/red]")

    # ── Session end: generate daily log ────────────────────────────
    if conversation_had_messages:
        try:
            messages = await session_manager.get_messages(session.session_id)
            if messages:
                # Build a brief summary of the conversation
                summary_parts = []
                for msg in messages[-10:]:  # Last 10 messages
                    role = msg.role.value if hasattr(msg.role, "value") else str(msg.role)
                    summary_parts.append(f"{role}: {msg.content[:100]}")
                summary = "\n".join(summary_parts)

                daily = memory_store.get_file("daily")
                existing = daily.read()
                new_entry = f"## 会话摘要\n{summary}"
                if existing:
                    daily.write(f"{existing}\n\n{new_entry}")
                else:
                    daily.write(new_entry)

                # Archive old daily logs
                memory_store.archive_old_dailies(keep_days=2)
        except Exception:
            pass  # Daily log generation is best-effort


def _extract_search_keys(server_config: "ServerConfig") -> dict[str, str]:
    """Extract search API keys from server config environment."""
    return {
        "tavily_api_key": os.environ.get("TAVILY_API_KEY"),
        "serper_api_key": os.environ.get("SERPER_API_KEY"),
    }


def _build_gateway(server_config: "ServerConfig") -> "LLMGateway":
    """Build LLMGateway from ServerConfig, same logic as app.py."""
    from agentkit.llm.gateway import LLMGateway
    from agentkit.llm.providers.anthropic import AnthropicProvider
    from agentkit.llm.providers.gemini import GeminiProvider
    from agentkit.llm.providers.openai import OpenAICompatibleProvider

    gateway = LLMGateway(config=server_config.llm_config)

    for name, pconf in server_config.llm_config.providers.items():
        if not pconf.api_key:
            continue
        try:
            if pconf.type == "anthropic":
                provider = AnthropicProvider(
                    api_key=pconf.api_key,
                    model=list(pconf.models.keys())[0] if pconf.models else "claude-sonnet-4-20250514",
                    max_tokens=pconf.max_tokens,
                    base_url=pconf.base_url or "https://api.anthropic.com",
                    timeout=pconf.timeout,
                )
            elif pconf.type == "gemini":
                provider = GeminiProvider(
                    api_key=pconf.api_key,
                    model=list(pconf.models.keys())[0] if pconf.models else "gemini-2.0-flash",
                    max_output_tokens=pconf.max_tokens,
                    base_url=pconf.base_url or "https://generativelanguage.googleapis.com",
                    timeout=pconf.timeout,
                )
            else:
                provider = OpenAICompatibleProvider(
                    api_key=pconf.api_key,
                    base_url=pconf.base_url,
                )
            gateway.register_provider(name, provider)
        except Exception as e:
            import logging
            logging.getLogger(__name__).warning(f"Failed to register LLM provider '{name}': {e}")

    return gateway


def _resolve_default_model(server_config: "ServerConfig") -> str:
    """Resolve the default model from config."""
    if server_config.llm_config.model_aliases and "default" in server_config.llm_config.model_aliases:
        return server_config.llm_config.model_aliases["default"]
    # Fallback: first provider's first model
    for name, pconf in server_config.llm_config.providers.items():
        if pconf.api_key and pconf.models:
            first_model = list(pconf.models.keys())[0]
            return f"{name}/{first_model}"
    return "default"


def _load_dotenv(dotenv_path: str) -> None:
    """Load .env file into environment."""
    from pathlib import Path
    path = Path(dotenv_path)
    if not path.exists():
        return
    with open(path, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            if "=" not in line:
                continue
            key, _, value = line.partition("=")
            key = key.strip()
            value = value.strip().strip("\"'")
            if key and key not in os.environ:
                os.environ[key] = value


def _print_help() -> None:
    """Print chat command help."""
    rprint(Panel(
        "[bold]Chat Commands[/bold]\n\n"
        "  [cyan]/help[/cyan]    — Show this help\n"
        "  [cyan]/clear[/cyan]   — Clear conversation (new session)\n"
        "  [cyan]/model <name>[/cyan] — Switch LLM model\n"
        "  [cyan]/quit[/cyan]    — Exit chat\n\n"
        "[bold]Tips[/bold]\n\n"
        "  • Multi-line input: end a line with [cyan]\\[/cyan] to continue\n"
        "  • Your conversation is stored in memory for the session",
        border_style="dim",
    ))
