"""Modern CLI using Typer framework."""

import sys
from pathlib import Path
from typing import Annotated

import typer
from rich.console import Console

from fwauto.cli_commands.config import ensure_project_initialized
from fwauto.graph import create_firmware_graph
from fwauto.logging_config import init_project_logging
from fwauto.nodes.chat import classify_error_for_ai_repair
from fwauto.state import FirmwareState

# Create Typer app
app = typer.Typer(
    name="fwauto",
    help="🚀 STM32 Firmware Automation Tool with AI",
    add_completion=False,
    pretty_exceptions_enable=False,
    invoke_without_command=True,
)

# Auth sub-commands
auth_app = typer.Typer(help="🔐 Authentication commands")
app.add_typer(auth_app, name="auth")

# Config sub-commands
config_app = typer.Typer(help="⚙️ Configuration management")
app.add_typer(config_app, name="config")

# UART sub-commands
uart_app = typer.Typer(help="🔌 UART monitor commands")
app.add_typer(uart_app, name="uart", hidden=True)

console = Console()


@app.callback(invoke_without_command=True)
def callback(
    ctx: typer.Context,
    ai_log: str = typer.Option(
        None,
        "--ai-log",
        envvar="FWAUTO_AI_LOG",
        hidden=True,
    ),
):
    """
    🚀 FWAuto - Firmware Development Automation Tool

    Run without command to start interactive chat:
        fwauto
        fwauto "讓 LED 快速閃爍"
    """
    from .logging_config import setup_ai_logger

    setup_ai_logger(level=ai_log)  # handles None → config.toml → OFF fallback

    # 如果有明確的子命令，讓 typer 處理
    if ctx.invoked_subcommand is not None:
        return

    # 無子命令但有 options (e.g. --ai-log INFO)，進入互動 chat
    _enter_unified_chat_mode()


# ============================================================
# Auth Commands
# ============================================================


@auth_app.command(name="login")
def auth_login(
    dev: bool = typer.Option(False, "--dev", help="Use development mode (skip Google OAuth)"),
):
    """
    🔐 Login to FWAuto usage tracking server.

    Examples:
        fwauto auth login          # Google OAuth login
        fwauto auth login --dev    # Development mode (no OAuth)
    """
    from .auth import get_auth_manager

    auth = get_auth_manager()
    success = auth.ensure_authenticated(use_dev_login=dev)

    if success:
        console.print("[green]✅ Login successful[/green]")
    else:
        console.print("[red]❌ Login failed[/red]")
        raise typer.Exit(1)


@auth_app.command(name="logout")
def auth_logout():
    """
    🚪 Logout from FWAuto usage tracking server.

    Example:
        fwauto auth logout
    """
    from .auth import get_auth_manager

    auth = get_auth_manager()
    auth.logout()


@auth_app.command(name="status")
def auth_status():
    """
    📋 Show current authentication status.

    Example:
        fwauto auth status
    """
    from .auth import cli_status

    cli_status()


# ============================================================
# UART Commands
# ============================================================


@uart_app.command(name="start")
def uart_start():
    """
    ▶️ Start UART monitoring daemon.

    Starts a background daemon that monitors the serial port
    and writes received data to log files.

    The daemon has a 30-minute timeout and auto-restarts
    when any fwauto command is executed.

    Example:
        fwauto uart start
    """
    from .config import find_project_root, load_project_config
    from .uart_daemon import UartDaemon

    project_root = find_project_root()
    if not project_root:
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
        console.print("[yellow]Tip: Run 'fwauto build' or 'fwauto config' to initialize[/yellow]")
        raise typer.Exit(1)

    # Load config for serial port settings
    try:
        config = load_project_config(project_root)
        port = config.log_serial_port
        baudrate = config.log_baudrate
    except Exception as e:
        console.print(f"[red]❌ Failed to load config: {e}[/red]")
        raise typer.Exit(1)

    if not port:
        console.print("[red]❌ Serial port not configured[/red]")
        console.print("[yellow]Tip: Run 'fwauto config --log' to configure serial port[/yellow]")
        raise typer.Exit(1)

    daemon = UartDaemon(project_root)
    success = daemon.start(port, baudrate)

    if not success:
        raise typer.Exit(1)


@uart_app.command(name="stop")
def uart_stop():
    """
    ⏹️ Stop UART monitoring daemon.

    Example:
        fwauto uart stop
    """
    from .config import find_project_root
    from .uart_daemon import UartDaemon

    project_root = find_project_root()
    if not project_root:
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
        raise typer.Exit(1)

    daemon = UartDaemon(project_root)
    daemon.stop()


@uart_app.command(name="status")
def uart_status():
    """
    📊 Show UART daemon status.

    Displays daemon running state, PID, uptime, and log file info.

    Example:
        fwauto uart status
    """
    from .config import find_project_root
    from .uart_daemon import UartDaemon

    project_root = find_project_root()
    if not project_root:
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
        raise typer.Exit(1)

    daemon = UartDaemon(project_root)
    status = daemon.status()

    console.print("\n[cyan]🔌 UART Daemon Status[/cyan]")
    console.print("=" * 40)

    if status["running"]:
        console.print(f"[green]● Running[/green] (PID: {status['pid']})")

        if "uptime_seconds" in status:
            uptime = status["uptime_seconds"]
            minutes, seconds = divmod(uptime, 60)
            console.print(f"   Uptime: {minutes}m {seconds}s")

        if "start_time" in status:
            console.print(f"   Started: {status['start_time']}")
    else:
        console.print("[dim]○ Not running[/dim]")

    console.print(f"\n   PID file: {status['pid_file']}")
    console.print(f"   Log dir: {status['log_dir']}")

    if "latest_log" in status:
        size_kb = status.get("log_size", 0) / 1024
        console.print(f"   Latest log: {status['latest_log']} ({size_kb:.1f} KB)")

    console.print()


@uart_app.command(name="restart")
def uart_restart():
    """
    🔄 Restart UART monitoring daemon.

    Stops the daemon if running, then starts it again.
    Useful for resetting the 30-minute timeout.

    Example:
        fwauto uart restart
    """
    from .config import find_project_root, load_project_config
    from .uart_daemon import UartDaemon

    project_root = find_project_root()
    if not project_root:
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
        raise typer.Exit(1)

    # Load config for serial port settings
    try:
        config = load_project_config(project_root)
        port = config.log_serial_port
        baudrate = config.log_baudrate
    except Exception as e:
        console.print(f"[red]❌ Failed to load config: {e}[/red]")
        raise typer.Exit(1)

    if not port:
        console.print("[red]❌ Serial port not configured[/red]")
        console.print("[yellow]Tip: Run 'fwauto config --log' to configure serial port[/yellow]")
        raise typer.Exit(1)

    daemon = UartDaemon(project_root)
    success = daemon.restart(port, baudrate)

    if not success:
        raise typer.Exit(1)


@app.command(name="dashboard")
def dashboard_cmd(
    open_browser: bool = typer.Option(False, "--open", "-o", help="Open dashboard in browser"),
):
    """
    🌐 Show or open FWAuto Dashboard.

    Examples:
        fwauto dashboard          # Show dashboard URL
        fwauto dashboard --open   # Open in browser
    """
    import webbrowser

    from .auth import FWAUTO_SERVER_URL

    dashboard_url = f"{FWAUTO_SERVER_URL}/dashboard"

    console.print()
    console.print("🌐 FWAuto Dashboard")
    console.print("=" * 50)
    console.print(f"   URL: {dashboard_url}")
    console.print()

    if open_browser:
        console.print("[dim]Opening browser...[/dim]")
        webbrowser.open(dashboard_url)
    else:
        console.print("[dim]Tip: Use --open or -o to open browser directly[/dim]")
    console.print()


def _ensure_authenticated() -> bool:
    """
    Ensure user is authenticated before running AI commands.

    Returns True if authenticated, False otherwise.
    """
    from .auth import get_auth_manager

    auth = get_auth_manager()

    # 嘗試載入已有的 token
    if auth._load_config() and auth._verify_token():
        return True

    # Login required
    console.print("[yellow]⚠️  Not logged in. Login required to use AI features[/yellow]")
    console.print()

    # Ask if user wants to login
    try:
        do_login = typer.confirm("Login now?", default=True)
        if do_login:
            return auth.ensure_authenticated(use_dev_login=False)
        else:
            console.print("[dim]Tip: Run 'fwauto auth login' to login[/dim]")
            return False
    except (KeyboardInterrupt, EOFError):
        console.print("\n[yellow]Cancelled[/yellow]")
        return False


def _ensure_ai_ready() -> bool:
    """
    確保 AI 功能所需的所有前置條件。

    檢查：用戶已登入（Direct 模式跳過此檢查）

    Returns True if ready, False otherwise.
    """
    from .auth import is_direct_api_mode

    # Direct 模式跳過登入檢查（使用本機 Claude Max 訂閱）
    if is_direct_api_mode():
        return True

    # Proxy 模式檢查登入狀態
    if not _ensure_authenticated():
        return False

    return True


def _get_auth_status() -> str:
    """Get authentication status for display."""
    import json

    from .auth import AUTH_CONFIG_FILE, get_user_token

    token = get_user_token()
    if not token:
        return "❌ Not logged in"

    # Try to get email from config
    try:
        if AUTH_CONFIG_FILE.exists():
            with open(AUTH_CONFIG_FILE, encoding="utf-8") as f:
                config = json.load(f)
                email = config.get("email", "")
                if email:
                    return f"✅ {email}"
    except Exception:
        pass

    return "✅ Logged in"


def _get_help_info() -> str:
    """Generate help information string.

    Returns formatted help text including:
    - Version
    - Available commands
    - Environment info (Python, platform, project path)
    - Auth status
    """
    import platform
    from importlib.metadata import version

    from .auth import FWAUTO_SERVER_URL
    from .config import find_project_root

    # Get version
    try:
        ver = version("fwauto")
    except Exception:
        ver = "unknown"

    # Get project root
    project_root = find_project_root()
    project_path = str(project_root) if project_root else "Not in a project directory"

    # Get auth status
    auth_status = _get_auth_status()

    # Build help text
    help_text = f"""🚀 FWAuto v{ver}

📋 Available Commands:
  build       🔨 Build firmware project
  deploy      📥 Deploy firmware to device
  log         📊 Analyze UART log files
  config      ⚙️ View or modify project configuration
  auth        🔐 Authentication (login/logout/status)
  rag         📚 RAG document management (upload/list/delete/search)
  dashboard   🌐 Show Dashboard URL
  help        ❓ Show this help message

💡 Run fwauto (no arguments) to enter interactive mode

🔧 Environment Info:
  Python:    {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}
  Platform:  {platform.system().lower()}
  Project:   {project_path}

🔐 Auth Status: {auth_status}
🌐 Dashboard: {FWAUTO_SERVER_URL}/dashboard"""

    return help_text


def _build_deploy_state(
    binary_args: str,
    project_root: str,
) -> FirmwareState:
    """Build FirmwareState for deploy command.

    Used by both CLI deploy command and /deploy slash command.

    Args:
        binary_args: Binary arguments string
        project_root: Project root path

    Returns:
        FirmwareState configured for deploy
    """
    state: FirmwareState = {
        "user_prompt": "Deploy firmware to device",
        "project_root": project_root,
        "project_initialized": True,
        "execution_mode": "deploy",
        "command": "deploy",
        "command_args": {"binary_args": binary_args},
        "mode_metadata": {},
        "command_error": None,
        "build_status": "pending",
        "build_error_type": None,
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": None,
        "chat_completed": None,
        "config": None,
    }
    return state


def _build_log_state(
    log_path: str | None,
    analysis_prompt: str,
    project_root: str,
) -> FirmwareState:
    """Build FirmwareState for log command.

    Used by both CLI log command and /log slash command.
    Maintains double storage pattern for log_file_path.

    Args:
        log_path: Path to log file (None to auto-detect from config)
        analysis_prompt: Analysis prompt text
        project_root: Project root path

    Returns:
        FirmwareState configured for log analysis
    """
    state: FirmwareState = {
        "user_prompt": analysis_prompt,
        "project_root": project_root,
        "project_initialized": True,
        "execution_mode": "log",
        "command": "log",
        "command_args": {
            "log_path": log_path,
            "analysis_prompt": analysis_prompt,
        },
        "mode_metadata": {},
        "command_error": None,
        "build_status": "pending",
        "build_error_type": None,
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "log_file_path": log_path,  # Double storage (None is OK, log_node will handle)
        "log_analysis_result": None,
        "firmware_path": None,
        "chat_completed": None,
        "config": None,
    }
    return state


def run_workflow(state: FirmwareState) -> None:
    """Execute the firmware workflow with given state."""
    workflow = create_firmware_graph()

    try:
        # Stream workflow execution
        for event in workflow.stream(state):
            for node, result in event.items():
                # Display progress
                if node == "build":
                    status = result.get("build_status")
                    if status == "success":
                        console.print("[green]✓ Build succeeded[/green]")
                    elif status == "error":
                        iteration = result.get("iteration", 0)
                        max_retries = result.get("max_retries", 3)
                        console.print(f"[red]❌ Build failed (attempt {iteration}/{max_retries})[/red]")

                        # Display build log for error details
                        build_log = result.get("build_log", "")
                        if build_log:
                            console.print(f"[yellow]{build_log}[/yellow]")

                elif node == "deploy":
                    status = result.get("deploy_status")
                    if status == "success":
                        console.print("[green]✓ Deploy succeeded[/green]")

                        # Show remote log path if available
                        log_file_path = result.get("log_file_path")
                        if log_file_path:
                            console.print(f"[cyan]📊 Remote log: {log_file_path}[/cyan]")

                    elif status == "error":
                        console.print("[red]❌ Deploy failed[/red]")

                        # Get full error output for classification
                        errors = result.get("deploy_errors", [])
                        error_output = errors[0] if errors else ""

                        # Show brief error summary
                        if error_output:
                            error_summary = error_output.split("\n")[0]
                            if error_summary and error_summary != "Deploy failed: ":
                                console.print(f"[red]   {error_summary}[/red]")

                        # Classify error and show troubleshooting tips
                        should_offer_ai, prompt_message, _ = classify_error_for_ai_repair("deploy", error_output)

                        if not should_offer_ai:
                            # Infrastructure error - show troubleshooting tips
                            console.print(f"\n{prompt_message}")

                elif node == "log":
                    # Show log analysis result or error
                    log_result = result.get("log_analysis_result", "")
                    if log_result and log_result.startswith("Error:"):
                        console.print(f"[red]❌ {log_result}[/red]")

    except KeyboardInterrupt:
        console.print("\n[yellow]⚠️  Operation cancelled by user[/yellow]")
        sys.exit(130)
    except Exception as e:
        console.print(f"[red]❌ Workflow error: {e}[/red]")
        sys.exit(1)


@app.command()
def build():
    """
    🔨 Build firmware project.

    Auto-initializes if .fwauto/ configuration is missing.

    Example:
        fwauto build
    """
    # 自動觸發專案初始化（如需要）
    project_root = ensure_project_initialized()
    if not project_root:
        raise typer.Exit(1)

    console.print("[cyan]🔨 Building project...[/cyan]")

    state: FirmwareState = {
        "user_prompt": "build",
        "project_root": "",
        "project_initialized": False,
        "build_status": "pending",
        "build_error_type": None,
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "execution_mode": "build",
        "command": "build",
        "command_args": {},
        "mode_metadata": None,
        "command_error": None,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": None,
        "chat_completed": None,
        "config": None,
    }

    run_workflow(state)


@app.command()
def deploy(
    binary_args: str = typer.Option(
        "",
        "--binary-args",
        "-ba",
        help="Arguments to pass to the binary (e.g., 'on' or '--mode test')",
    ),
    scenario: str = typer.Option(
        "",
        "--scenario",
        "-s",
        help="Use a predefined scenario (overridden by --binary-args)",
    ),
):
    """
    📥 Deploy firmware to device.

    Auto-initializes if .fwauto/ configuration is missing.
    Supports passing arguments to the binary after deployment.

    \b
    Examples:
        fwauto deploy                             # Deploy without arguments
        fwauto deploy --binary-args "on"          # Pass single argument
        fwauto deploy -ba "arg1 arg2"             # Pass multiple arguments
        fwauto deploy --scenario led-on           # Use predefined scenario
        fwauto deploy -s test-mode                # Use scenario (short option)
    """
    # 自動觸發專案初始化（如需要）
    project_root_init = ensure_project_initialized()
    if not project_root_init:
        raise typer.Exit(1)

    console.print("[cyan]📥 Deploying firmware...[/cyan]")

    # 優先級：--binary-args > --scenario
    final_binary_args = binary_args

    if not final_binary_args and scenario:
        # 載入 scenario
        try:
            from .config import find_project_root, load_project_config

            project_root = find_project_root()
            if not project_root:
                console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
                raise typer.Exit(1)

            config = load_project_config(project_root)
            scenario_manager = config.get_scenario_manager()

            scenario_obj = scenario_manager.get_scenario(scenario)
            if not scenario_obj:
                console.print(f"[red]❌ Scenario '{scenario}' not found[/red]")
                console.print("[yellow]💡 Check scenarios in .fwauto/config.toml[/yellow]")
                raise typer.Exit(1)

            final_binary_args = scenario_obj.binary_args
            console.print(f"[green]✓[/green] Using scenario: {scenario_obj.name}")
            console.print(f"[dim]  Description: {scenario_obj.description}[/dim]")
            console.print(f"[dim]  Args: {scenario_obj.binary_args}[/dim]")

        except typer.Exit:
            raise
        except Exception as e:
            console.print(f"[red]❌ Failed to load scenario: {e}[/red]")
            raise typer.Exit(1)

    if final_binary_args:
        console.print(f"[cyan]📦 Binary args: {final_binary_args}[/cyan]")

    # Get project config for helper function
    from .config import find_project_root, load_project_config

    project_root_path = find_project_root()
    if not project_root_path:
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
        raise typer.Exit(1)

    # Use helper function to build state
    state = _build_deploy_state(final_binary_args, str(project_root_path))

    run_workflow(state)


@app.command(hidden=True)
def run(
    binary_args: str = typer.Option(
        "",
        "--binary-args",
        "-ba",
        help="Arguments to pass to the binary (e.g., 'on' or '--mode test')",
    ),
    scenario: str = typer.Option(
        "",
        "--scenario",
        "-s",
        help="Use a predefined scenario (overridden by --binary-args)",
    ),
):
    """
    ⚡ Run build and deploy.

    Auto-initializes if .fwauto/ configuration is missing.
    Builds the project and deploys to device with optional binary arguments.

    \b
    Examples:
        fwauto run                                # Build and deploy
        fwauto run --binary-args "on"             # Build, deploy with argument
        fwauto run -ba "arg1 arg2"                # Build, deploy with multiple args
        fwauto run --scenario led-on              # Build, deploy with scenario
        fwauto run -s test-mode                   # Build, deploy with scenario (short)
    """
    # 自動觸發專案初始化（如需要）
    project_root_init = ensure_project_initialized()
    if not project_root_init:
        raise typer.Exit(1)

    console.print("[cyan]⚡ Running build and deploy...[/cyan]")

    # 優先級：--binary-args > --scenario
    final_binary_args = binary_args

    if not final_binary_args and scenario:
        # 載入 scenario
        try:
            from .config import find_project_root, load_project_config

            project_root = find_project_root()
            if not project_root:
                console.print("[red]❌ Not in a FWAuto project (no .fwauto/ found)[/red]")
                raise typer.Exit(1)

            config = load_project_config(project_root)
            scenario_manager = config.get_scenario_manager()

            scenario_obj = scenario_manager.get_scenario(scenario)
            if not scenario_obj:
                console.print(f"[red]❌ Scenario '{scenario}' not found[/red]")
                console.print("[yellow]💡 Check scenarios in .fwauto/config.toml[/yellow]")
                raise typer.Exit(1)

            final_binary_args = scenario_obj.binary_args
            console.print(f"[green]✓[/green] Using scenario: {scenario_obj.name}")
            console.print(f"[dim]  Description: {scenario_obj.description}[/dim]")
            console.print(f"[dim]  Args: {scenario_obj.binary_args}[/dim]")

        except typer.Exit:
            raise
        except Exception as e:
            console.print(f"[red]❌ Failed to load scenario: {e}[/red]")
            raise typer.Exit(1)

    if final_binary_args:
        console.print(f"[cyan]📦 Binary args: {final_binary_args}[/cyan]")

    state: FirmwareState = {
        "user_prompt": "run",
        "project_root": "",
        "project_initialized": False,
        "build_status": "pending",
        "build_error_type": None,
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "execution_mode": "run",
        "command": "run",
        "command_args": {"binary_args": final_binary_args},
        "mode_metadata": {"deploy_after_build": True},
        "command_error": None,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": None,
        "chat_completed": None,
        "config": None,
    }

    run_workflow(state)


@app.command(name="log")
def log_command(
    prompt: str = typer.Argument(..., help="Analysis question or query"),
    log_path: str | None = typer.Option(
        None,
        "--log-path",
        "-l",
        help="Path to UART log file (local or remote: user@host:/path). If not provided, uses last log from config.",
    ),
):
    """
    📊 Analyze UART log files using AI.

    Supports both local and remote log files via SSH/SCP.

    Examples:
        fwauto log "有任何 error 嗎?"
        fwauto log "LED的閃爍頻率為何?" --log-path logs/uart.log
        fwauto log "開機訊息" --log-path user@192.168.1.100:/var/log/uart.log
    """
    # 自動觸發專案初始化（如需要）
    project_root_path = ensure_project_initialized()
    if not project_root_path:
        raise typer.Exit(1)

    # 檢查 AI 功能是否可用
    if not _ensure_ai_ready():
        raise typer.Exit(1)

    if log_path:
        console.print(f"[cyan]📊 Analyzing log: {log_path}[/cyan]")
    else:
        console.print("[cyan]📊 Using log path from last deployment...[/cyan]")
    console.print(f"[cyan]❓ Question: {prompt}[/cyan]")

    # Use helper function to build state (pass None if not provided)
    state = _build_log_state(log_path, prompt, str(project_root_path))

    run_workflow(state)


@app.command(hidden=True)
def feat(
    prompt: str = typer.Argument(..., help="Feature description in natural language"),
):
    """
    🚀 AI-assisted firmware feature implementation.

    Automatically implements new firmware features with AI assistance,
    including code generation, compilation, auto-fix, and deployment.

    Examples:
        fwauto feat "實作摩斯密碼編碼，支援字符 A-Z"
        fwauto feat "加入蜂鳴器控制，發出 100ms 短音和 300ms 長音"
    """
    # 檢查 AI 功能是否可用
    if not _ensure_ai_ready():
        raise typer.Exit(1)

    console.print("[cyan]🚀 AI Feature Implementation[/cyan]")
    console.print(f"[yellow]Feature: {prompt}[/yellow]\n")

    state: FirmwareState = {
        "user_prompt": prompt,
        "project_root": "",
        "project_initialized": False,
        "build_status": "pending",
        "build_error_type": None,
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "execution_mode": "feat",
        "command": "feat",
        "command_args": {"feature_prompt": prompt},
        "mode_metadata": {"deploy_after_build": True},
        "command_error": None,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": None,
        "chat_completed": None,
        "config": None,
    }

    run_workflow(state)


# ============================================================
# RAG Sub-commands
# ============================================================

rag_app = typer.Typer(help="📚 RAG document search")
app.add_typer(rag_app, name="rag")


def _get_rag_client():
    """Get RAG client instance."""
    import os

    from .rag_client import RAGClient

    base_url = os.environ.get("FWAUTO_RAG_URL", "https://pdf-rag-webapp-tztzmlgglq-de.a.run.app")
    return RAGClient(base_url=base_url)


# ============================================================
# RAG Interactive Mode
# ============================================================


def _rag_input(prompt_text: str) -> str:
    """Get user input for RAG interactive mode. Uses console.input() for async compatibility."""
    return console.input(f"[cyan]{prompt_text}>[/cyan] ").strip()


def _rag_interactive_list():
    """Interactive list: show docs → select one → action (search/delete)."""
    client = _get_rag_client()

    try:
        docs = client.list_documents()
    except Exception as e:
        console.print(f"[red]❌ Failed to list documents: {e}[/red]")
        raise typer.Exit(1)

    if not docs:
        console.print("[yellow]No documents found.[/yellow]")
        return

    # Display numbered list
    console.print(f"\n[bold]📚 Documents ({len(docs)})[/bold]")
    for i, doc in enumerate(docs, 1):
        filename = doc.get("filename", doc.get("name", "unknown"))
        status = doc.get("status", "unknown")
        pages = doc.get("pages", doc.get("page_count", "?"))
        status_color = "green" if status == "ready" else "yellow" if status == "processing" else "red"
        console.print(f"  [{i}] {filename}  [{status_color}]{status}[/{status_color}]  {pages} pages")
    console.print()

    # Select document
    try:
        choice = _rag_input(f"Select document [1-{len(docs)}]")
    except (EOFError, KeyboardInterrupt):
        return

    if not choice:
        return

    try:
        idx = int(choice) - 1
        if idx < 0 or idx >= len(docs):
            console.print("[red]❌ Invalid selection[/red]")
            return
    except ValueError:
        console.print("[red]❌ Please enter a number[/red]")
        return

    doc = docs[idx]
    doc_id = doc.get("id", doc.get("doc_id", ""))
    filename = doc.get("filename", doc.get("name", "unknown"))

    # Action menu
    console.print(f"\n[bold]📄 {filename}[/bold]")
    console.print("  [1] search - Search this document")
    console.print("  [2] delete - Delete this document")
    console.print()

    try:
        action = _rag_input("Action [1-2]")
    except (EOFError, KeyboardInterrupt):
        return

    if action == "1":
        try:
            query = _rag_input("🔍 Search query")
        except (EOFError, KeyboardInterrupt):
            return
        if query:
            _rag_do_search(client, query, doc_id=doc_id)
    elif action == "2":
        confirm = typer.confirm(f"Delete {filename}?", default=False)
        if confirm:
            try:
                client.delete_document(doc_id)
                console.print(f"[green]✅ Deleted: {filename}[/green]")
            except Exception as e:
                console.print(f"[red]❌ Delete failed: {e}[/red]")


def _rag_do_search(client, question: str, doc_id: str | None = None):
    """Execute search and display results."""
    console.print(f"[cyan]🔍 Searching: {question}[/cyan]")
    if doc_id:
        console.print(f"[dim]Document: {doc_id}[/dim]")

    try:
        result = client.search(question, doc_id=doc_id)
    except Exception as e:
        console.print(f"[red]❌ Search failed: {e}[/red]")
        return

    contexts = result.get("contexts", [])
    if not contexts:
        console.print("[yellow]No results found.[/yellow]")
        return

    console.print(f"[green]Found {len(contexts)} result(s):[/green]\n")

    for i, ctx in enumerate(contexts, 1):
        score = ctx.get("score", 0)
        doc_name = ctx.get("doc_name", "unknown")
        page = ctx.get("page", "?")
        text = ctx.get("text", "")

        preview = text[:200] + "..." if len(text) > 200 else text
        preview = preview.replace("\n", " ")

        score_color = "green" if score > 0.7 else "yellow" if score > 0.4 else "red"
        console.print(f"[bold][{i}][/bold] [{score_color}]{score:.3f}[/{score_color}] 📄 {doc_name} (p.{page})")
        console.print(f"    [dim]{preview}[/dim]")
        console.print()


@rag_app.command(name="upload")
def rag_upload(
    path: Path = typer.Argument(..., help="PDF file path to upload"),
):
    """
    📤 Upload a PDF file for RAG processing.

    Examples:
        fwauto rag upload ./datasheet.pdf
    """
    from rich.progress import Progress, SpinnerColumn, TextColumn

    client = _get_rag_client()

    console.print(f"[cyan]📤 Uploading: {path.name}[/cyan]")

    try:
        result = client.upload(path)
    except FileNotFoundError:
        console.print(f"[red]❌ File not found: {path}[/red]")
        raise typer.Exit(1)
    except ValueError as e:
        console.print(f"[red]❌ {e}[/red]")
        raise typer.Exit(1)
    except Exception as e:
        console.print(f"[red]❌ Upload failed: {e}[/red]")
        raise typer.Exit(1)

    doc_id: str = result.get("doc_id", "")
    job_id: str = result.get("job_id", "")
    console.print(f"[green]✅ Uploaded! doc_id={doc_id}[/green]")
    console.print(f"[dim]Processing job: {job_id}[/dim]")

    # Wait for processing
    console.print("[cyan]⏳ Waiting for processing...[/cyan]")
    try:
        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            console=console,
        ) as progress:
            task = progress.add_task("Processing...", total=None)
            import time

            from .rag_client import RAGError

            start = time.time()
            not_found_count = 0
            while True:
                try:
                    status = client.get_job_status(job_id)
                    not_found_count = 0
                except RAGError as e:
                    if "not found" in str(e).lower():
                        not_found_count += 1
                        if not_found_count >= 5:
                            # Check if document is already ready
                            try:
                                docs = client.list_documents()
                                doc = next((d for d in docs if d["id"] == doc_id), None)
                                if doc and doc.get("status") == "ready":
                                    break
                            except Exception:
                                pass
                            console.print(f"[red]❌ Job lost (server may have restarted). doc_id={doc_id}[/red]")
                            console.print("[dim]Check document status with: fwauto rag search or the API[/dim]")
                            raise typer.Exit(1)
                        progress.update(task, description="Reconnecting...")
                        time.sleep(3)
                        continue
                    raise

                stage = status.get("stage", "")
                msg = status.get("message", "")

                progress.update(task, description=f"{stage}: {msg}" if msg else f"Stage: {stage}")

                if stage == "complete":
                    break
                if stage == "error":
                    console.print(f"[red]❌ Processing failed: {msg}[/red]")
                    raise typer.Exit(1)
                if time.time() - start > 600:
                    console.print("[red]❌ Timeout (10 min)[/red]")
                    raise typer.Exit(1)

                time.sleep(2)

    except KeyboardInterrupt:
        console.print(f"\n[yellow]Processing continues in background. job_id={job_id}[/yellow]")
        raise typer.Exit(0)

    console.print(f"[green]✅ Processing complete! doc_id={doc_id}[/green]")


@rag_app.command(name="status")
def rag_status(
    job_id: str = typer.Argument(..., help="Job ID from upload"),
):
    """
    📋 Check upload processing status.

    Examples:
        fwauto rag status 550e8400-e29b-41d4-a716-446655440000
    """
    client = _get_rag_client()

    try:
        status = client.get_job_status(job_id)
    except Exception as e:
        console.print(f"[red]❌ {e}[/red]")
        raise typer.Exit(1)

    stage = status.get("stage", "unknown")
    progress = status.get("progress", 0)
    total = status.get("total", 0)
    message = status.get("message", "")

    stage_colors = {
        "pending": "dim",
        "text": "cyan",
        "table": "cyan",
        "vision": "yellow",
        "vectorize": "yellow",
        "complete": "green",
        "error": "red",
    }
    color = stage_colors.get(stage, "white")

    console.print(f"[{color}]Stage: {stage}[/{color}]")
    if total > 0:
        console.print(f"Progress: {progress}/{total}")
    if message:
        console.print(f"Message: {message}")


@rag_app.command(name="list")
def rag_list():
    """
    📋 List all uploaded documents.

    Examples:
        fwauto rag list
    """
    from rich.table import Table

    client = _get_rag_client()

    try:
        docs = client.list_documents()
    except Exception as e:
        console.print(f"[red]❌ Failed to list documents: {e}[/red]")
        raise typer.Exit(1)

    if not docs:
        console.print("[yellow]No documents found.[/yellow]")
        raise typer.Exit(0)

    table = Table(title=f"📚 Documents ({len(docs)})")
    table.add_column("doc_id", style="cyan")
    table.add_column("Filename", style="white")
    table.add_column("Status", style="green")
    table.add_column("Pages", justify="right")
    table.add_column("Uploaded", style="dim")

    for doc in docs:
        doc_id = doc.get("id", doc.get("doc_id", "?"))
        filename = doc.get("filename", doc.get("name", "unknown"))
        status = doc.get("status", "unknown")
        pages = str(doc.get("pages", doc.get("page_count", "?")))
        uploaded = doc.get("created_at", doc.get("uploaded_at", "?"))

        status_style = "green" if status == "ready" else "yellow" if status == "processing" else "red"
        table.add_row(doc_id, filename, f"[{status_style}]{status}[/{status_style}]", pages, uploaded)

    console.print(table)


@rag_app.command(name="delete")
def rag_delete(
    doc_ids: list[str] | None = typer.Argument(None, help="Document ID(s) to delete"),
    all_docs: bool = typer.Option(False, "--all", "-a", help="Delete all documents"),
    force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
):
    """
    🗑️ Delete uploaded document(s).

    Examples:
        fwauto rag delete bb430ee4                  # Delete one
        fwauto rag delete bb430ee4 0036ff05         # Delete multiple
        fwauto rag delete --all                     # Delete all
        fwauto rag delete --all --force             # Delete all, skip confirmation
    """
    client = _get_rag_client()

    if all_docs:
        try:
            docs = client.list_documents()
        except Exception as e:
            console.print(f"[red]❌ Failed to list documents: {e}[/red]")
            raise typer.Exit(1)

        if not docs:
            console.print("[yellow]No documents to delete.[/yellow]")
            raise typer.Exit(0)

        if not force:
            confirm = typer.confirm(f"Delete ALL {len(docs)} documents?", default=False)
            if not confirm:
                console.print("[yellow]Cancelled.[/yellow]")
                raise typer.Exit(0)

        deleted = 0
        for doc in docs:
            did = doc.get("id", doc.get("doc_id", ""))
            try:
                client.delete_document(did)
                console.print(f"[green]✅ Deleted: {did} ({doc.get('filename', doc.get('name', ''))})[/green]")
                deleted += 1
            except Exception as e:
                console.print(f"[red]❌ Failed to delete {did}: {e}[/red]")

        console.print(f"\n[green]Deleted {deleted}/{len(docs)} documents.[/green]")
        return

    if not doc_ids:
        console.print("[red]❌ Provide doc_id(s) or use --all[/red]")
        raise typer.Exit(1)

    if not force and len(doc_ids) > 1:
        confirm = typer.confirm(f"Delete {len(doc_ids)} documents?", default=False)
        if not confirm:
            console.print("[yellow]Cancelled.[/yellow]")
            raise typer.Exit(0)
    elif not force:
        confirm = typer.confirm(f"Delete document {doc_ids[0]}?", default=False)
        if not confirm:
            console.print("[yellow]Cancelled.[/yellow]")
            raise typer.Exit(0)

    for did in doc_ids:
        try:
            client.delete_document(did)
            console.print(f"[green]✅ Deleted: {did}[/green]")
        except Exception as e:
            console.print(f"[red]❌ Failed to delete {did}: {e}[/red]")


@rag_app.command(name="search")
def rag_search(
    question: str = typer.Argument(..., help="Search query"),
    doc_id: str | None = typer.Option(None, "--doc-id", "-d", help="Limit search to specific document"),
    top_k: int = typer.Option(15, "--top-k", "-k", help="Number of results"),
):
    """
    🔍 Search uploaded documents.

    Examples:
        fwauto rag search "VCI voltage range"
        fwauto rag search "GPIO config" --doc-id abc123
        fwauto rag search "power supply" --top-k 5
    """
    client = _get_rag_client()

    console.print(f"[cyan]🔍 Searching: {question}[/cyan]")
    if doc_id:
        console.print(f"[dim]Document: {doc_id}[/dim]")

    try:
        result = client.search(question, doc_id=doc_id, top_k=top_k)
    except Exception as e:
        console.print(f"[red]❌ Search failed: {e}[/red]")
        raise typer.Exit(1)

    contexts = result.get("contexts", [])
    if not contexts:
        console.print("[yellow]No results found.[/yellow]")
        raise typer.Exit(0)

    console.print(f"[green]Found {len(contexts)} result(s):[/green]\n")

    for i, ctx in enumerate(contexts, 1):
        score = ctx.get("score", 0)
        doc_name = ctx.get("doc_name", "unknown")
        page = ctx.get("page", "?")
        text = ctx.get("text", "")

        preview = text[:200] + "..." if len(text) > 200 else text
        preview = preview.replace("\n", " ")

        score_color = "green" if score > 0.7 else "yellow" if score > 0.4 else "red"
        console.print(f"[bold][{i}][/bold] [{score_color}]{score:.3f}[/{score_color}] 📄 {doc_name} (p.{page})")
        console.print(f"    [dim]{preview}[/dim]")
        console.print()


@app.command(hidden=True)
def chat(
    message: Annotated[
        str | None,
        typer.Argument(help="Initial message to send (optional)"),
    ] = None,
):
    """
    💬 Interactive chat with AI.

    Start an interactive conversation session to:
    - Ask questions about your project
    - Request code modifications or new features
    - Get help with debugging and analysis
    - Explore codebase structure

    Requires .fwauto/ configuration in current directory or parent directories.

    Examples:
        fwauto chat
        fwauto chat "讓 LED 快速閃爍"
    """
    # 檢查 AI 功能是否可用
    if not _ensure_ai_ready():
        raise typer.Exit(1)

    if message:
        console.print(f"[cyan]💬 Starting chat with: {message}[/cyan]")
    else:
        console.print("[cyan]💬 Starting interactive chat session...[/cyan]")

    state: FirmwareState = {
        "user_prompt": message or "chat",
        "project_root": "",
        "project_initialized": False,
        "build_status": "pending",
        "build_error_type": None,
        "build_errors": [],
        "build_log": "",
        "deploy_status": "pending",
        "deploy_errors": [],
        "deploy_log": "",
        "iteration": 0,
        "max_retries": 3,
        "execution_mode": "chat",
        "command": "chat",
        "command_args": {"initial_message": message} if message else {},
        "mode_metadata": None,
        "command_error": None,
        "log_file_path": None,
        "log_analysis_result": None,
        "firmware_path": None,
        "chat_completed": None,
        "config": None,
    }

    run_workflow(state)


@config_app.callback(invoke_without_command=True)
def config_callback(
    ctx: typer.Context,
    log: bool = typer.Option(False, "--log", help="Configure log settings"),
):
    """
    ⚙️ View or modify FWAuto project configuration.

    Examples:
        fwauto config                              # Show all settings
        fwauto config --log                        # Configure log settings
        fwauto config get build.type               # Get a specific value
        fwauto config set deployment.board_ip 192.168.1.100  # Set a value
    """
    # If a subcommand was invoked, let it handle
    if ctx.invoked_subcommand is not None:
        return

    project_root = Path.cwd()

    if log:
        from .cli_commands.config import run_log_wizard

        config_path = project_root / ".fwauto" / "config.toml"

        if not config_path.exists():
            console.print("[red]❌ Not in a FWAuto project (no .fwauto/config.toml)[/red]")
            console.print("[yellow]Tip: Run 'fwauto build' or 'fwauto deploy' to initialize a project[/yellow]")
            sys.exit(1)

        console.print("[cyan]⚙️ FWAuto Log Configuration[/cyan]")
        success = run_log_wizard(config_path)
        sys.exit(0 if success else 1)

    # Default: show all config
    from .cli_commands.config import config_show

    config_path = project_root / ".fwauto" / "config.toml"

    if not config_path.exists():
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/config.toml)[/red]")
        console.print("[yellow]Tip: Run 'fwauto build' or 'fwauto deploy' to initialize a project[/yellow]")
        sys.exit(1)

    config_show(config_path)


@config_app.command(name="get")
def config_get_cmd(
    key: str = typer.Argument(..., help="Config key (e.g., build.type)"),
):
    """
    🔍 Get a configuration value.

    Examples:
        fwauto config get build.type
        fwauto config get deployment.board_ip
    """
    from .cli_commands.config import config_get

    config_path = Path.cwd() / ".fwauto" / "config.toml"

    if not config_path.exists():
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/config.toml)[/red]")
        raise typer.Exit(1)

    try:
        _, value = config_get(key, config_path)
        if value is not None:
            console.print(f"  {key} = {value}")
        else:
            console.print(f"[yellow]⚠️ Key '{key}' not found[/yellow]")
    except ValueError as e:
        console.print(f"[red]❌ {e}[/red]")
        raise typer.Exit(1)


@config_app.command(name="set")
def config_set_cmd(
    key: str = typer.Argument(..., help="Config key (e.g., build.type)"),
    value: str = typer.Argument(..., help="Value to set"),
):
    """
    ✏️ Set a configuration value.

    Examples:
        fwauto config set build.type command
        fwauto config set deployment.board_ip 192.168.1.100
    """
    from .cli_commands.config import config_set

    config_path = Path.cwd() / ".fwauto" / "config.toml"

    if not config_path.exists():
        console.print("[red]❌ Not in a FWAuto project (no .fwauto/config.toml)[/red]")
        raise typer.Exit(1)

    try:
        config_set(key, value, config_path)
        console.print(f"[green]✅ Set {key} = {value}[/green]")
    except ValueError as e:
        console.print(f"[red]❌ {e}[/red]")
        raise typer.Exit(1)


@app.command(name="help")
def help_command():
    """
    ❓ Show help information.

    Displays available commands, version, environment info, and API key status.

    Example:
        fwauto help
    """
    console.print(_get_help_info())


def _enter_unified_chat_mode() -> None:
    """Enter unified chat interface mode.

    This replaces the old _interactive_mode() function.
    Uses chat_node for interactive conversation with slash command support.
    """
    from .logging_config import get_logger

    logger = get_logger(__name__)

    try:
        # 自動觸發專案初始化（如需要）
        project_root = ensure_project_initialized()
        if not project_root:
            console.print("[red]❌ Setup cancelled[/red]")
            sys.exit(1)

        console.print(f"[cyan]📁 Project: {project_root}[/cyan]")
        console.print("[cyan]💬 Entering chat mode (type /exit or Ctrl+D to quit)[/cyan]")
        console.print("[dim]Tip: Use /build, /deploy, /log to run commands[/dim]\n")

        # Load project configuration for language settings
        from .config import load_project_config

        config = load_project_config(project_root)

        # Build initial state for chat mode
        state: FirmwareState = {
            "user_prompt": "",
            "project_root": str(project_root),
            "project_initialized": True,
            "execution_mode": "chat",
            "command": "chat",
            "command_args": {},
            "mode_metadata": {},
            "command_error": None,
            "build_status": "pending",
            "build_error_type": None,
            "build_errors": [],
            "build_log": "",
            "deploy_status": "pending",
            "deploy_errors": [],
            "deploy_log": "",
            "iteration": 0,
            "max_retries": 3,
            "log_file_path": None,
            "log_analysis_result": None,
            "firmware_path": None,
            "chat_completed": None,
            "config": config,
        }

        # Create and execute graph (which will route to chat_node)
        app_graph = create_firmware_graph()
        result = app_graph.invoke(state)

        logger.debug(f"💬 Chat: Graph result chat_completed = {result.get('chat_completed')}")
        if result.get("chat_completed"):
            console.print("\n[green]✅ Chat ended[/green]")
        else:
            console.print("\n[yellow]⚠️ Chat ended abnormally[/yellow]")

    except KeyboardInterrupt:
        # User pressed Ctrl+C, treat as normal exit
        console.print("\n[green]✅ Chat ended[/green]")

    except FileNotFoundError as e:
        console.print(f"[red]❌ Project not found: {e}[/red]")
        console.print("[yellow]Tip: Run this command in a FWAuto project directory (with .fwauto/)[/yellow]")
        sys.exit(1)

    except Exception as e:
        logger.error(f"Chat mode failed: {e}", exc_info=True)
        console.print(f"[red]❌ Failed to start chat mode: {e}[/red]")
        sys.exit(1)


def _load_banners() -> list[str]:
    """Load ASCII banners from banners.txt, split by '---' separator."""
    import importlib.resources

    try:
        text = importlib.resources.files("fwauto").joinpath("banners.txt").read_text(encoding="utf-8")
        banners = [b.strip("\n") for b in text.split("\n---\n") if b.strip()]
        if banners:
            return banners
    except Exception:
        pass
    # Fallback
    return [
        "░▒█▀▀▀░▒█░░▒█░█▀▀▄░▒█░▒█░▀▀█▀▀░▒█▀▀▀█\n"
        "░▒█▀▀░░▒█▒█▒█▒█▄▄█░▒█░▒█░░▒█░░░▒█░░▒█\n"
        "░▒█░░░░▒▀▄▀▄▀▒█░▒█░░▀▄▄▀░░▒█░░░▒█▄▄▄█"
    ]


def _print_banner():
    """Print a randomly selected FWAuto ASCII banner with version."""
    import random
    from importlib.metadata import version

    try:
        ver = version("fwauto")
    except Exception:
        ver = "unknown"

    banner = random.choice(_load_banners())  # noqa: S311
    console.print(f"[cyan]{banner}[/cyan]")
    console.print(f"[dim]v{ver}[/dim]\n")


def main():
    """Entry point for the CLI."""
    # Print banner
    _print_banner()

    # Initialize logging first (ensures debug logs for all commands)
    init_project_logging()

    # AI API logging (picks up config.toml if configured, no-op if OFF)
    from .logging_config import setup_ai_logger

    setup_ai_logger()

    # Auto-restart UART daemon if running (resets 30-minute timer)
    from .uart_daemon import restart_daemon_if_running

    restart_daemon_if_running()

    # Check if running in unified chat mode (no arguments or with message)
    if len(sys.argv) == 1:
        _enter_unified_chat_mode()
    else:
        # 檢查第一個參數是否為子命令
        subcommand_names = {
            "build",
            "deploy",
            "run",
            "log",
            "feat",
            "chat",
            "config",
            "help",
            "auth",
            "rag",
            "dashboard",
            "uart",
        }
        first_arg = sys.argv[1] if len(sys.argv) > 1 else ""

        # 如果第一個參數是選項 (--xxx) 或有效子命令，讓 typer 處理
        if first_arg.startswith("-") or first_arg.lower() in subcommand_names:
            app()
        else:
            # 第一個參數是 chat message
            # 進入帶 message 的 chat 模式
            from fwauto.cli_commands.config import ensure_project_initialized

            project_root = ensure_project_initialized()
            if project_root:
                chat(first_arg)
            else:
                console.print("[red]❌ Setup cancelled[/red]")
                sys.exit(1)


if __name__ == "__main__":
    main()
