"""REPL main loop - the heart of Tsumugi."""

from __future__ import annotations

import argparse
import io
import os
import sys
from typing import Any


def _ensure_utf8() -> None:
    """Force UTF-8 on Windows to avoid cp932 encoding errors."""
    if sys.platform == "win32":
        os.environ.setdefault("PYTHONUTF8", "1")
        if hasattr(sys.stdout, "reconfigure"):
            sys.stdout.reconfigure(encoding="utf-8", errors="replace")
        if hasattr(sys.stderr, "reconfigure"):
            sys.stderr.reconfigure(encoding="utf-8", errors="replace")


_ensure_utf8()

from tsumugi.config import build_config, Config, find_instruction_files
from tsumugi.context import compact_messages, estimate_conversation_tokens
from tsumugi.persona import build_system_prompt
from tsumugi.agent import run_agent_turn
from tsumugi.permissions import PermissionManager
from tsumugi.plugins import load_plugins
from tsumugi.tools import create_default_registry
from tsumugi.ui import display
from tsumugi.ui.prompt import create_session, get_input


def create_provider(config: Config):
    """Create the appropriate LLM provider based on config."""
    if config.provider == "anthropic":
        from tsumugi.providers.anthropic import AnthropicProvider

        if not config.api_key:
            display.print_error(
                "ANTHROPIC_API_KEY not set.\n"
                "Set via env var or ~/.tsumugi/config.json"
            )
            sys.exit(1)
        return AnthropicProvider(api_key=config.api_key, model=config.model)
    elif config.provider == "openai":
        from tsumugi.providers.openai_compat import OpenAICompatProvider

        # Ollama and some local providers don't need an API key
        api_key = config.api_key or "ollama"
        return OpenAICompatProvider(
            api_key=api_key,
            model=config.model,
            base_url=config.base_url,
        )
    else:
        display.print_error(f"Unknown provider: {config.provider}")
        sys.exit(1)


def _handle_repl_command(
    command: str,
    messages: list[dict[str, Any]],
    state: dict[str, Any],
    config: Config,
) -> bool:
    """Handle a /command. Returns True if handled, False otherwise."""
    cmd = command.lower().split()[0]
    args = command.split()[1:] if len(command.split()) > 1 else []

    if cmd == "/clear":
        messages.clear()
        display.console.print("[bright_cyan]会話履歴をクリアしました！[/bright_cyan]")
        return True

    elif cmd == "/context":
        stats = estimate_conversation_tokens(messages, state["system_prompt"])
        display.console.print()
        display.console.print("[bold bright_cyan]Context Usage[/bold bright_cyan]")
        display.console.print(
            f"  System prompt:  ~{stats['system']:,} tokens"
        )
        display.console.print(
            f"  Messages:       ~{stats['messages']:,} tokens "
            f"({stats['message_count']} messages)"
        )
        display.console.print(
            f"  Total:          ~{stats['total']:,} tokens"
        )
        display.console.print()
        return True

    elif cmd == "/compact":
        old_count = len(messages)
        keep = int(args[0]) if args else 6
        messages[:], info = compact_messages(messages, keep_recent=keep)
        if info:
            display.console.print(f"[bright_cyan]{info}[/bright_cyan]")
        else:
            display.console.print("[dim]圧縮するほどの履歴がありません[/dim]")
        return True

    elif cmd == "/model":
        if not args:
            display.console.print(
                f"[bright_cyan]現在のモデル: {config.model}[/bright_cyan]"
            )
            display.console.print(
                "[dim]変更: /model <model_name>[/dim]"
            )
        else:
            new_model = args[0]
            config.model = new_model
            # Recreate provider with new model
            try:
                state["provider"] = create_provider(config)
                display.console.print(
                    f"[bright_cyan]モデルを {new_model} に変更しました！[/bright_cyan]"
                )
            except Exception as e:
                display.print_error(f"モデル変更失敗: {e}")
        return True

    elif cmd == "/provider":
        if not args:
            available = list(config.providers.keys())
            display.console.print(
                f"[bright_cyan]現在のプロバイダー: {config.provider}[/bright_cyan]"
            )
            if available:
                display.console.print(
                    f"[dim]利用可能: {', '.join(available)}[/dim]"
                )
            display.console.print(
                "[dim]変更: /provider <name>[/dim]"
            )
        else:
            new_provider = args[0].lower()
            if new_provider not in config.providers:
                display.print_error(
                    f"プロバイダー '{new_provider}' のAPIキーが設定されていません"
                )
            else:
                pc = config.providers[new_provider]
                config.provider = new_provider
                config.api_key = pc.api_key
                config.base_url = pc.base_url
                if pc.model:
                    config.model = pc.model
                try:
                    state["provider"] = create_provider(config)
                    display.console.print(
                        f"[bright_cyan]プロバイダーを {new_provider} "
                        f"(model: {config.model}) に変更しました！[/bright_cyan]"
                    )
                except Exception as e:
                    display.print_error(f"プロバイダー変更失敗: {e}")
        return True

    elif cmd == "/plan":
        if not args:
            state["plan_mode"] = not state["plan_mode"]
        elif args[0].lower() == "on":
            state["plan_mode"] = True
        elif args[0].lower() == "off":
            state["plan_mode"] = False
        else:
            display.console.print("[dim]Usage: /plan [on|off][/dim]")
            return True
        mode_str = "ON" if state["plan_mode"] else "OFF"
        state["system_prompt"] = build_system_prompt(config, plan_mode=state["plan_mode"])
        display.console.print(f"[bright_cyan]Plan Mode: {mode_str}[/bright_cyan]")
        return True

    elif cmd == "/reload":
        instruction_files = find_instruction_files(config.working_dir)
        config.instructions = [content for _, content in instruction_files]
        state["system_prompt"] = build_system_prompt(config, plan_mode=state.get("plan_mode", False))
        display.console.print(
            f"[bright_cyan]TSUMUGI.md を再読み込みしました "
            f"({len(config.instructions)} file(s))[/bright_cyan]"
        )
        return True

    elif cmd == "/help":
        display.console.print()
        display.console.print("[bold bright_cyan]Commands[/bold bright_cyan]")
        display.console.print("  /clear              会話履歴をクリア")
        display.console.print("  /context            コンテキスト使用量を表示")
        display.console.print("  /compact [n]        古いメッセージを要約 (直近n件を保持, default: 6)")
        display.console.print("  /model [name]       モデルの表示・変更")
        display.console.print("  /plan [on|off]      プランモードの切り替え")
        display.console.print("  /provider [name]    プロバイダーの表示・切り替え")
        display.console.print("  /reload             TSUMUGI.md を再読み込み")
        display.console.print("  /help               このヘルプを表示")
        display.console.print("  exit, /quit         終了")
        display.console.print()
        return True

    return False


def run_repl(config: Config) -> None:
    """Run the main REPL loop."""
    provider = create_provider(config)
    system_prompt = build_system_prompt(config)
    registry = create_default_registry()
    plugin_names = load_plugins(registry)
    permissions = PermissionManager()
    messages: list[dict[str, Any]] = []
    session = create_session()
    # Mutable state so REPL commands can swap provider/system_prompt
    state: dict[str, Any] = {
        "provider": provider,
        "system_prompt": system_prompt,
        "plan_mode": False,
    }

    display.print_welcome(
        provider=config.provider,
        model=config.model,
        working_dir=config.working_dir,
        tool_names=registry.names,
        instructions_count=len(config.instructions),
        has_memory=bool(config.memory),
    )

    while True:
        try:
            user_input = get_input(session, config.user_name)
        except KeyboardInterrupt:
            display.console.print("\n[dim]Ctrl+C[/dim]")
            continue

        if user_input is None:
            display.console.print("\n[bright_cyan]先輩、またね～！[/bright_cyan]")
            break

        text = user_input.strip()
        if not text:
            continue
        if text.lower() in ("exit", "quit", "/exit", "/quit"):
            display.console.print("[bright_cyan]先輩、またね～！[/bright_cyan]")
            break

        # Handle REPL commands
        if text.startswith("/"):
            if _handle_repl_command(text, messages, state, config):
                continue

        # Append user message
        messages.append({"role": "user", "content": text})

        # Run agent turn (handles streaming, tool calls, and looping)
        try:
            run_agent_turn(
                messages=messages,
                provider=state["provider"],
                system_prompt=state["system_prompt"],
                registry=registry,
                permissions=permissions,
                plan_mode=state.get("plan_mode", False),
                max_tokens=config.max_tokens,
            )
        except KeyboardInterrupt:
            display.console.print("\n[dim]Interrupted[/dim]")
        except Exception as e:
            display.print_error(f"Error: {e}")
            # Remove user message on failure
            if messages and messages[-1].get("role") == "user":
                messages.pop()


def parse_args() -> argparse.Namespace:
    """Parse command-line arguments."""
    parser = argparse.ArgumentParser(
        prog="tsumugi",
        description="Tsumugi AI Agent - Claude Code-like coding assistant",
        epilog=(
            "Examples:\n"
            "  tsumugi                                          # Anthropic (default)\n"
            "  tsumugi --provider openai --model gpt-4o         # OpenAI\n"
            "  tsumugi --provider openai --base-url http://localhost:11434/v1 --model llama3.1  # Ollama\n"
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "--provider",
        choices=["anthropic", "openai"],
        help="LLM provider (default: anthropic)",
    )
    parser.add_argument("--model", help="Model name")
    parser.add_argument("--api-key", help="API key")
    parser.add_argument("--base-url", help="Base URL (for OpenAI-compatible)")
    parser.add_argument(
        "--max-tokens", type=int, help="Max response tokens (default: 8192)"
    )
    return parser.parse_args()


def main() -> None:
    """Entry point."""
    args = parse_args()
    config = build_config(
        cli_provider=args.provider,
        cli_model=args.model,
        cli_api_key=args.api_key,
        cli_base_url=args.base_url,
        cli_max_tokens=args.max_tokens,
    )

    run_repl(config)
