"""CLI entry point for gemcli.

Supports both verb-first and noun-first command styles:
    gemcli chat "hello"          (noun-first)
    gemcli gems list             (noun-first)
    gemcli list gems             (verb-first alias)

All commands call the service layer directly.
"""

from __future__ import annotations

import asyncio
import os
import platform
import shutil
import subprocess
import sys
from pathlib import Path

import rich_click as click
from loguru import logger
from rich.console import Console
from rich.markdown import Markdown

from gemini_web_mcp_cli import __version__
from gemini_web_mcp_cli.core.client import GeminiClient
from gemini_web_mcp_cli.core.exceptions import AuthError, GeminiError, TokenFactoryError
from gemini_web_mcp_cli.core.profiles import ProfileManager

console = Console()


def run_async(coro):
    """Run an async function synchronously."""
    return asyncio.run(coro)


def _handle_token_factory_error(e: Exception, command: str = "") -> None:
    """Print a detailed Token Factory error with troubleshooting guidance."""
    console.print(f"[red]Token Factory error: {e}[/red]")
    console.print(
        "[dim]This feature requires the Pro model, which needs Chrome to generate "
        "a BotGuard token.[/dim]"
    )
    console.print("[dim]Possible causes:[/dim]")
    console.print("[dim]  - Chrome is not installed or not found on PATH[/dim]")
    console.print(
        "[dim]  - Another Chrome instance (or orphaned process) is using the debug port "
        "(9222-9231)[/dim]"
    )
    console.print(
        "[dim]  - Running in a background/headless context (LaunchAgent, cron, SSH) "
        "where Chrome cannot launch[/dim]"
    )
    console.print("[dim]To fix:[/dim]")
    console.print("[dim]  1. Close all Chrome instances and retry[/dim]")
    console.print(
        "[dim]  2. Kill orphaned Chrome processes: "
        "pkill -f 'remote-debugging-port'[/dim]"
    )
    console.print("[dim]  3. Run 'gemcli login' from an interactive terminal[/dim]")
    console.print("[dim]  4. Run 'gemcli doctor --verbose' to diagnose[/dim]")
    console.print("[dim]  5. Start persistent Chrome: gemcli chrome start[/dim]")
    if command == "chat":
        console.print(
            "[dim]  Tip: 'gemcli chat' works without Chrome when using the Flash model "
            "(default)[/dim]"
        )


def _print_version(ctx, param, value):
    """Callback for --version flag."""
    if not value or ctx.resilient_parsing:
        return
    console.print(f"gemcli version {__version__}")
    ctx.exit()


def _print_ai_docs(ctx, param, value):
    """Callback for --ai flag. Outputs AI-friendly documentation."""
    if not value or ctx.resilient_parsing:
        return
    click.echo(f"""# gemcli CLI - AI Assistant Guide

You are interacting with `gemcli`, a command-line interface for Google Gemini
(gemini.google.com). This documentation teaches you how to use the tool effectively.

## Version

gemcli version {__version__}

---

## CRITICAL: Authentication

Before ANY operation, ensure the user is authenticated.

### First-Time Setup
```bash
gemcli login
```
Prompts for cookies (__Secure-1PSID and __Secure-1PSIDTS) from Chrome DevTools.
If NotebookLM MCP profiles exist, offers to reuse them.

### Check If Authenticated
```bash
gemcli login --check
```
Validates credentials by fetching tokens from gemini.google.com.

---

## Chat
```bash
gemcli chat "What is quantum computing?"
gemcli chat "Explain this" -m gemini-3.0-pro
gemcli chat "Summarize" -o summary.md
gemcli chat "Analyze this" -f document.pdf
gemcli chat "Compare these" -f file1.md -f file2.md
```

## File Upload
```bash
gemcli chat "What is in this image?" -f screenshot.png
gemcli chat "Summarize this document" -f report.pdf
gemcli file upload document.pdf       # Upload and get identifier
```

## Image Generation (Nano Banana / Imagen)
Requires Chrome for BotGuard token and tool activation. Run `gemcli chrome start` first.
```bash
gemcli image "A red panda in a bamboo forest"
gemcli image "Futuristic city" -o city.png
```

## Video Generation (Veo 3.1)
Requires Chrome for BotGuard token and tool activation. Run `gemcli chrome start` first.
```bash
gemcli video "Ocean waves at sunset"
gemcli video "Dancing robot" -o robot.mp4
```

## Music Generation (Lyria 3)
Generate 30-second music tracks with vocals, lyrics, instrumentals, and cover art.
```bash
gemcli music "A comical R&B slow jam about a sock"
gemcli music "Cats playing video games" -s 8-bit -o track.mp3
gemcli music "Summer vibes" -s k-pop -o video.mp4 -f video
gemcli music --list-styles                  # Show all 16 style presets
```

Options:
- `--style, -s`: Style preset (e.g., 8-bit, k-pop, cinematic, emo, afropop)
- `--output, -o`: Save to file (.mp3 for audio, .mp4 for video)
- `--format, -f`: Output format: audio (default) or video (MP4 with cover art)
- `--list-styles`: Show all available style presets and exit

Available Style Presets: 90s-rap, latin-pop, folk-ballad, 8-bit, workout, reggaeton,
rnb-romance, kawaii-metal, cinematic, emo, afropop, forest-bath, k-pop,
birthday-roast, folk-a-cappella, bad-music.

Aliases: rap, latin, folk, chiptune, edm, rnb, r&b, kawaii, metal, orchestral,
ambient, kpop, birthday, roast, country, acappella, bad.

## Deep Research
```bash
gemcli research "Latest AI advances in 2026"
```

## Usage Limits
Check per-feature usage quotas, remaining count, and reset time.
Requires Chrome for browser cookies (Token Factory).
```bash
gemcli limits
```

## Hack Claude (Launch Claude Code via Gemini)
```bash
gemcli hack claude                    # Launch with Gemini Flash (default)
gemcli hack claude --model gemini-pro # Use Gemini Pro
gemcli hack claude -p "fix this bug" # Pass args to Claude Code
```

## Gems (Custom System Prompts)
```bash
gemcli gems list
gemcli gems create --name "Reviewer" --prompt "You are a code reviewer..."
gemcli gems delete <gem_id>
```

## Profile Management
```bash
gemcli profile list              # List all profiles
gemcli profile switch <name>     # Switch active profile
gemcli profile create <name>     # Create new profile
gemcli profile delete <name>     # Delete profile
```

## MCP Setup (configure AI tools)
```bash
gemcli setup list                # Show configured clients
gemcli setup add cursor          # Add to Cursor
gemcli setup add codex            # Add to Codex
gemcli setup add all             # Interactive — detect and configure all tools
gemcli setup remove cursor       # Remove from Cursor
gemcli setup remove all          # Remove from all configured tools
```

## Configuration
```bash
gemcli config show               # Show all settings
gemcli config get active_profile # Get specific value
gemcli config set key value      # Set a value
```

## Diagnostics
```bash
gemcli doctor                    # Check installation and auth
gemcli doctor --verbose          # Detailed diagnostics
```

## Skills (teach AI tools to use gemcli)
```bash
gemcli skill list                # Show installation status
gemcli skill install claude-code # Install for Claude Code
gemcli skill install cursor      # Install for Cursor
gemcli skill update              # Update all outdated skills
gemcli skill show                # Display skill content
```

## Available Models
- gemini-3.0-pro
- gemini-3.0-flash
- gemini-3.0-flash-thinking
- gemini-3.0-flash-preview (default)

## Verb-First Aliases
These aliases let you use verb-first style:
```bash
gemcli list gems                 # = gemcli gems list
gemcli list profiles             # = gemcli profile list
gemcli list skills               # = gemcli skill list
gemcli create gem --name ...     # = gemcli gems create --name ...
gemcli delete gem <id>           # = gemcli gems delete <id>
gemcli install skill <tool>      # = gemcli skill install <tool>
gemcli update skill [tool]       # = gemcli skill update [tool]
```

## Global Options
- `--profile, -p`: Use a specific profile for this command
- `--verbose, -v`: Enable debug logging
- `--version`: Show version
- `--ai`: Output this AI-friendly documentation
""")
    ctx.exit()


# ── Global options ──────────────────────────────────────────────────────────


def _install_completion(ctx, param, value):
    """Callback for --install-completion flag."""
    if not value or ctx.resilient_parsing:
        return

    import os
    shell = os.environ.get("SHELL", "")
    if "zsh" in shell:
        script = 'eval "$(_GEMCLI_COMPLETE=zsh_source gemcli)"'
        rc_file = Path.home() / ".zshrc"
        shell_name = "zsh"
    elif "bash" in shell:
        script = 'eval "$(_GEMCLI_COMPLETE=bash_source gemcli)"'
        rc_file = Path.home() / ".bashrc"
        shell_name = "bash"
    elif "fish" in shell:
        script = '_GEMCLI_COMPLETE=fish_source gemcli | source'
        rc_file = Path.home() / ".config" / "fish" / "completions" / "gemcli.fish"
        shell_name = "fish"
    else:
        console.print(f"[yellow]Unknown shell: {shell}[/yellow]")
        console.print("Supported: bash, zsh, fish")
        ctx.exit(1)

    marker = "# gemcli completion"
    if rc_file.exists() and marker in rc_file.read_text():
        console.print(f"[green]Completion already installed in {rc_file}[/green]")
        ctx.exit()

    with open(rc_file, "a") as f:
        f.write(f"\n{marker}\n{script}\n")
    console.print(f"[green]Installed {shell_name} completion in {rc_file}[/green]")
    console.print(f"Restart your shell or run: source {rc_file}")
    ctx.exit()


def _show_completion(ctx, param, value):
    """Callback for --show-completion flag."""
    if not value or ctx.resilient_parsing:
        return

    import os
    shell = os.environ.get("SHELL", "")
    if "zsh" in shell:
        click.echo('eval "$(_GEMCLI_COMPLETE=zsh_source gemcli)"')
    elif "bash" in shell:
        click.echo('eval "$(_GEMCLI_COMPLETE=bash_source gemcli)"')
    elif "fish" in shell:
        click.echo('_GEMCLI_COMPLETE=fish_source gemcli | source')
    else:
        click.echo(f"# Unknown shell: {shell}")
        click.echo('# For bash: eval "$(_GEMCLI_COMPLETE=bash_source gemcli)"')
        click.echo('# For zsh:  eval "$(_GEMCLI_COMPLETE=zsh_source gemcli)"')
        click.echo('# For fish: _GEMCLI_COMPLETE=fish_source gemcli | source')
    ctx.exit()


@click.group()
@click.option("--profile", "-p", default=None, help="Profile to use (overrides active)")
@click.option("--verbose", is_flag=True, help="Enable verbose logging")
@click.option("--version", is_flag=True, callback=_print_version, expose_value=False,
              is_eager=True, help="Show version and exit")
@click.option("--ai", is_flag=True, callback=_print_ai_docs, expose_value=False,
              is_eager=True, help="Output AI-friendly documentation for this CLI")
@click.option("--install-completion", is_flag=True, callback=_install_completion,
              expose_value=False, is_eager=True,
              help="Install shell completion for the current shell")
@click.option("--show-completion", is_flag=True, callback=_show_completion,
              expose_value=False, is_eager=True,
              help="Show completion script for the current shell")
@click.pass_context
def cli(ctx, profile, verbose):
    """gemcli - CLI for the Gemini web interface."""
    ctx.ensure_object(dict)
    ctx.obj["profile"] = profile
    if not verbose:
        logger.remove()
        logger.add(sys.stderr, level="WARNING")


# ── Login ───────────────────────────────────────────────────────────────────


@cli.command()
@click.option("--profile", "-p", default=None, help="Profile name to login to")
@click.option("--check", is_flag=True, help="Only check if current auth is valid")
@click.option(
    "--manual", "-m", is_flag=True,
    help="Manually paste cookies instead of automated Chrome login",
)
@click.option(
    "--force", "-f", is_flag=True,
    help="Force a fresh login by clearing existing cookies",
)
@click.pass_context
def login(ctx, profile, check, manual, force):
    """Authenticate with Gemini.

    By default, launches Chrome and extracts cookies automatically (same as NotebookLM MCP).
    Use --manual to paste cookies from DevTools instead.
    Use --check to validate existing auth without re-login.
    """
    pm = ProfileManager()
    profile_name = profile or ctx.obj.get("profile") or pm.get_active_profile()

    # --check: just validate auth, don't re-login
    if check:
        from gemini_web_mcp_cli.core.auth import AuthManager

        auth = AuthManager(pm, profile_name=profile_name)
        state = auth.load()

        if not state.is_valid:
            console.print("[red]Not authenticated.[/red] Run [bold]gemcli login[/bold].")
            raise SystemExit(1)

        console.print(f"[bold]Profile:[/bold] {profile_name}")
        if state.email:
            console.print(f"[bold]Account:[/bold] {state.email}")
        console.print(f"[bold]Has cookies:[/bold] {'yes' if state.cookies else 'no'}")
        console.print(f"[bold]Has token:[/bold] {'yes' if state.tokens.snlm0e else 'no'}")

        console.print("Validating tokens...")
        try:
            run_async(auth.refresh_tokens())
            if auth.state.email and not state.email:
                console.print(f"[bold]Account:[/bold] {auth.state.email}")
            console.print("[green]Authentication is valid.[/green]")
        except AuthError as e:
            console.print(f"[yellow]Tokens may be expired: {e}[/yellow]")
            console.print("Run [bold]gemcli login[/bold] to refresh.")
            raise SystemExit(1)
        except Exception as e:
            console.print(f"[red]Validation failed: {e}[/red]")
            console.print("Run [bold]gemcli login[/bold] to refresh.")
            raise SystemExit(1)
        return

    # First-run: detect NotebookLM MCP profiles and let the user decide
    chrome_profile_path = None
    if pm.is_first_run() and pm.has_nlm_profiles():
        nlm_profiles = pm._list_nlm_profiles()
        console.print(
            "\n[bold]NotebookLM MCP profiles detected![/bold]\n"
            "\nYou have existing NotebookLM MCP profiles that use the same\n"
            "Google account. You can reuse them or create a fresh profile.\n"
        )

        # Show numbered options for NLM profiles + fresh option
        for i, name in enumerate(nlm_profiles, 1):
            console.print(f"  [bold]{i})[/bold] Use NotebookLM profile: {name}")
        all_idx = len(nlm_profiles) + 1
        fresh_idx = len(nlm_profiles) + 2
        console.print(f"  [bold]{all_idx})[/bold] Use all NotebookLM profiles (import all)")
        console.print(f"  [bold]{fresh_idx})[/bold] Create a new profile just for Gemini\n")

        valid_choices = [str(i) for i in range(1, fresh_idx + 1)]
        while True:
            choice = click.prompt("Your choice", default="1")
            choice = choice.strip()
            if choice in valid_choices:
                break
            console.print(f"[red]Invalid choice. Please enter {', '.join(valid_choices)}[/red]")
        choice_num = int(choice)

        if choice_num <= len(nlm_profiles):
            # Use a specific NLM profile
            chosen = nlm_profiles[choice_num - 1]
            pm.set_active_profile(chosen)
            profile_name = chosen
            chrome_profile_path = pm.get_chrome_profile_path(chosen)
            console.print(f"\n[green]Using NotebookLM profile '{chosen}'.[/green]\n")
        elif choice_num == all_idx:
            # Import all -- set the first one as active
            profile_name = nlm_profiles[0]
            pm.set_active_profile(profile_name)
            chrome_profile_path = pm.get_chrome_profile_path(profile_name)
            console.print("\n[green]All NotebookLM profiles are now available:[/green]")
            for name in nlm_profiles:
                marker = " [green]<- active[/green]" if name == profile_name else ""
                console.print(f"  {name}{marker}")
            console.print()
        else:
            # Fresh profile
            profile_name = click.prompt("Profile name", default="default")
            console.print(f"\nCreating fresh profile: {profile_name}\n")

    # Get Chrome profile path if not already set
    if not chrome_profile_path:
        chrome_profile_path = pm.get_chrome_profile_path(profile_name)

    # ── Manual mode: paste cookies ──────────────────────────────────────
    if manual:
        console.print(
            f"[bold]Manual login for profile:[/bold] {profile_name}\n"
            "\nTo authenticate, provide your Gemini cookies:\n"
            "1. Open https://gemini.google.com in Chrome\n"
            "2. Open DevTools (F12) > Application > Cookies\n"
            "3. Copy the values of __Secure-1PSID and __Secure-1PSIDTS\n"
        )

        psid = click.prompt("__Secure-1PSID")
        psidts = click.prompt("__Secure-1PSIDTS")

        from gemini_web_mcp_cli.core.auth import AuthManager

        auth = AuthManager(pm, profile_name=profile_name)
        auth.save_cookies({"__Secure-1PSID": psid, "__Secure-1PSIDTS": psidts})

        console.print(f"\n[green]Cookies saved for profile '{profile_name}'.[/green]")
        console.print("Fetching tokens...")

        try:
            run_async(auth.refresh_tokens())
            console.print("[green]Authentication successful![/green]")
        except AuthError as e:
            console.print(f"[red]Token extraction failed: {e}[/red]")
        return

    # ── Automated mode: launch Chrome via CDP ───────────────────────────
    # Detect if this is a new/unauthenticated profile — if so, force a
    # visible Chrome window so the user can sign into a different account.
    auth_data = pm.load_auth(profile_name)
    is_new_profile = not auth_data or not auth_data.get("cookies", {}).get("__Secure-1PSID")

    if force:
        console.print(
            "[yellow]Force flag used: Clearing existing cookies "
            "to force a fresh login...[/yellow]"
        )
        is_new_profile = True
        pm.save_auth({}, profile_name)
        if chrome_profile_path:
            from pathlib import Path
            cookies_file = Path(chrome_profile_path) / "Default" / "Cookies"
            journal_file = Path(chrome_profile_path) / "Default" / "Cookies-journal"
            if cookies_file.exists():
                cookies_file.unlink()
            if journal_file.exists():
                journal_file.unlink()

    console.print(f"[bold]Profile:[/bold]        {profile_name}")
    if chrome_profile_path:
        console.print(f"[bold]Chrome profile:[/bold] {chrome_profile_path}")

    if is_new_profile:
        console.print()
        console.print(
            "[bold yellow]New profile — opening Chrome so you can sign in.[/bold yellow]\n"
            "Sign in with your Google account in the Chrome window.\n"
            "gemcli will detect the login automatically (up to 5 minutes).\n"
        )
    else:
        console.print()
        console.print("Launching Chrome to extract cookies automatically...\n")

    def _on_waiting():
        console.print(
            "[yellow]Waiting for you to log in...[/yellow]\n"
            "Sign in with your Google account in the Chrome window.\n"
            "gemcli will detect the login automatically (up to 5 minutes).\n"
        )

    try:
        from gemini_web_mcp_cli.core.cdp import extract_cookies_via_cdp

        result = extract_cookies_via_cdp(
            profile_name=profile_name,
            chrome_profile_path=chrome_profile_path,
            on_waiting_for_login=_on_waiting,
            force_new_session=is_new_profile,
        )

        cookies = result["cookies"]
        tokens = result.get("tokens", {})
        email = result.get("email", "")
        final_chrome_path = result.get("chrome_profile_path") or chrome_profile_path

        from gemini_web_mcp_cli.core.auth import AuthManager, AuthState, AuthTokens

        auth = AuthManager(pm, profile_name=profile_name)
        auth._state = AuthState(
            cookies=cookies,
            tokens=AuthTokens(
                snlm0e=tokens.get("snlm0e", ""),
                cfb2h=tokens.get("cfb2h", ""),
                fdrfje=tokens.get("fdrfje", ""),
            ),
            chrome_profile_path=final_chrome_path,
            email=email,
        )
        auth.save()

        if tokens.get("snlm0e"):
            console.print("[green]Authentication successful![/green]")
            if email:
                console.print(f"[dim]Account: {email}[/dim]")
            console.print(
                f"[dim]Cookies and tokens saved for profile '{profile_name}'.[/dim]"
            )
        else:
            console.print("[yellow]Cookies saved but session token (SNlM0e) "
                          "not found.[/yellow]")
            console.print("Try [bold]gemcli login --force[/bold] to start a "
                          "fresh login session.")

    except AuthError as e:
        console.print(f"[red]Automated login failed: {e}[/red]")
        console.print("\nFalling back to manual mode...")
        console.print("You can also run [bold]gemcli login --manual[/bold] directly.\n")

        psid = click.prompt("__Secure-1PSID")
        psidts = click.prompt("__Secure-1PSIDTS")

        from gemini_web_mcp_cli.core.auth import AuthManager

        auth = AuthManager(pm, profile_name=profile_name)
        auth.save_cookies({"__Secure-1PSID": psid, "__Secure-1PSIDTS": psidts})

        console.print("Fetching tokens...")
        try:
            run_async(auth.refresh_tokens())
            console.print("[green]Authentication successful![/green]")
        except AuthError as e2:
            console.print(f"[red]Token extraction failed: {e2}[/red]")


# ── Profile management ──────────────────────────────────────────────────────


@cli.group()
def profile():
    """Manage authentication profiles."""
    pass


@profile.command("list")
def profile_list():
    """List all profiles (shows both gemcli-native and NotebookLM shared)."""
    pm = ProfileManager()
    profiles = pm.list_profiles_detailed()
    active = pm.get_active_profile()

    if not profiles:
        console.print("No profiles found. Run [bold]gemcli login[/bold] to create one.")
        return

    for p in profiles:
        active_marker = " [green]<- active[/green]" if p.name == active else ""
        source_marker = " [dim](NotebookLM)[/dim]" if p.source == "nlm" else ""
        email_marker = ""
        auth_data = pm.load_auth(p.name)
        if auth_data.get("email"):
            email_marker = f" [dim]({auth_data['email']})[/dim]"
        console.print(f"  {p.name}{email_marker}{source_marker}{active_marker}")


@profile.command("switch")
@click.argument("name")
def profile_switch(name):
    """Switch the active profile."""
    pm = ProfileManager()
    try:
        pm.set_active_profile(name)
        console.print(f"[green]Switched to profile: {name}[/green]")
    except GeminiError as e:
        console.print(f"[red]{e}[/red]")


@profile.command("create")
@click.argument("name")
def profile_create(name):
    """Create a new empty profile."""
    pm = ProfileManager()
    try:
        pm.create_profile(name)
        console.print(f"[green]Created profile: {name}[/green]")
    except GeminiError as e:
        console.print(f"[red]{e}[/red]")


@profile.command("delete")
@click.argument("name")
@click.confirmation_option(prompt="Are you sure you want to delete this profile?")
def profile_delete(name):
    """Delete a profile."""
    pm = ProfileManager()
    try:
        pm.delete_profile(name)
        console.print(f"[green]Deleted profile: {name}[/green]")
    except GeminiError as e:
        console.print(f"[red]{e}[/red]")


@profile.command("rename")
@click.argument("old_name")
@click.argument("new_name")
def profile_rename(old_name, new_name):
    """Rename a profile.

    Renames the profile directory, Chrome profile, Token Factory profile,
    and updates auth.json and config.json as needed.

    Example:
        gemcli profile rename debug production
    """
    pm = ProfileManager()
    try:
        pm.rename_profile(old_name, new_name)
        console.print(f"[green]Renamed profile: {old_name} → {new_name}[/green]")
    except GeminiError as e:
        console.print(f"[red]{e}[/red]")


@profile.command("sources")
def profile_sources():
    """Show where profiles come from (gemcli-native vs NotebookLM shared)."""
    pm = ProfileManager()
    profiles = pm.list_profiles_detailed()

    if not profiles:
        console.print("No profiles found.")
        return

    gemcli_count = sum(1 for p in profiles if p.source == "gemcli")
    nlm_count = sum(1 for p in profiles if p.source == "nlm")

    console.print("\n[bold]Profile sources:[/bold]")
    console.print(f"  gemcli-native: {gemcli_count} profiles at ~/.gemini-web-mcp-cli/profiles/")
    console.print(f"  NotebookLM:    {nlm_count} profiles at ~/.notebooklm-mcp-cli/profiles/")
    console.print("\nNotebookLM profiles are live -- new NLM profiles automatically appear here.")


# ── Chat ────────────────────────────────────────────────────────────────────


CHAT_SLASH_COMMANDS_HELP = """
[bold]Slash commands:[/bold]
  /model <name>   Switch model (pro, flash, thinking)
  /model          Show current model
  /verify         Show server-confirmed model from last response
  /new            Start a new conversation
  /save <file>    Save conversation to file
  /history        Show conversation turns so far
  /help           Show this help
  /quit           Exit chat (also: Ctrl+C, Ctrl+D)
""".strip()


@cli.command()
@click.argument("prompt", required=False, default=None)
@click.option("--model", "-m", default=None,
              help="Model: pro, flash, thinking, preview (or full name)")
@click.option("--output", "-o", default=None, help="Save response to file")
@click.option("--new", "new_conversation", is_flag=True, help="Force a new conversation")
@click.option(
    "--file", "-f", "files", multiple=True,
    help="Attach files to the prompt (can be used multiple times)"
)
@click.pass_context
def chat(ctx, prompt, model, output, new_conversation, files):
    """Chat with Gemini. Interactive mode if no prompt given.

    \b
    Single message:
        gemcli chat "What is 2+2?"
        gemcli chat "Explain this" -m pro
        gemcli chat "Summarize" -o summary.md
        gemcli chat "Analyze this" -f document.pdf
    \b
    Interactive mode:
        gemcli chat
        gemcli chat -m thinking
    \b
    In interactive mode, use /help to see slash commands.
    """
    profile_name = ctx.obj.get("profile")

    # Resolve model: explicit flag > config default > built-in default
    pm = ProfileManager()
    if not model:
        cfg = pm._read_config()
        model = cfg.get("default_model")

    files_list = list(files) if files else None

    if prompt or files_list:
        # Single message mode (if files are provided without prompt, we still send)
        prompt = prompt or "Describe the attached files."
        _chat_single(profile_name, prompt, model, output, files_list)
    else:
        # Interactive mode
        _chat_interactive(profile_name, model)


def _chat_single(profile_name, prompt, model, output, files=None):
    """Send a single chat message."""

    async def _run():
        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.chat import ChatService

            svc = ChatService(client)

            if files:
                console.print(f"[dim]Uploading {len(files)} file(s)...[/dim]")

            response = await svc.send(prompt, model=model, files=files)

            if output:
                Path(output).write_text(response.text or "")
                console.print(f"[green]Response saved to {output}[/green]")
            else:
                if response.text:
                    console.print(Markdown(response.text))
                else:
                    console.print("[yellow]No text in response.[/yellow]")

                if response.has_images:
                    images = response.candidates[0].generated_images
                    if images:
                        count = len(images)
                        console.print(
                            f"\n[bold magenta]Generated {count} image(s):[/bold magenta]"
                        )
                        for img in images:
                            try:
                                url = img[0][3][3] if isinstance(img, list) else str(img)
                                console.print(f"[link={url}]{url}[/link]")
                            except (IndexError, TypeError):
                                pass

    try:
        run_async(_run())
    except TokenFactoryError as e:
        _handle_token_factory_error(e, command="chat")
    except AuthError as e:
        console.print(f"[red]Auth error: {e}[/red]")
    except GeminiError as e:
        console.print(f"[red]Error: {e}[/red]")


def _chat_interactive(profile_name, initial_model):
    """Interactive chat REPL with slash commands."""
    from gemini_web_mcp_cli.core.constants import get_model

    current_model = initial_model
    model_display = current_model or "default"
    history: list[dict] = []
    last_server_model_hash: str | None = None
    last_server_model_label: str | None = None

    async def _run():
        nonlocal current_model, model_display, history
        nonlocal last_server_model_hash, last_server_model_label

        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.chat import ChatService

            svc = ChatService(client)

            console.print("[bold]Gemini Interactive Chat[/bold]")
            console.print(f"Model: {model_display} | Type /help for commands | /quit to exit\n")

            while True:
                try:
                    user_input = console.input("[bold green]> [/bold green]").strip()
                except (EOFError, KeyboardInterrupt):
                    console.print("\n[dim]Goodbye![/dim]")
                    break

                if not user_input:
                    continue

                # ── Slash commands ──────────────────────────────────
                if user_input.startswith("/"):
                    parts = user_input.split(maxsplit=1)
                    cmd = parts[0].lower()
                    arg = parts[1].strip() if len(parts) > 1 else ""

                    if cmd in ("/quit", "/exit", "/q"):
                        console.print("[dim]Goodbye![/dim]")
                        break

                    elif cmd == "/help":
                        console.print(CHAT_SLASH_COMMANDS_HELP)

                    elif cmd == "/model":
                        if arg:
                            try:
                                get_model(arg)
                                current_model = arg
                                model_display = arg
                                console.print(f"[green]Switched to model: {arg}[/green]")
                                if arg in ("pro", "thinking"):
                                    console.print(
                                        "[dim]Pro/Thinking requires Chrome — "
                                        "first message may be slower.[/dim]"
                                    )
                            except ValueError as e:
                                console.print(f"[red]{e}[/red]")
                        else:
                            console.print(f"Current model: {model_display}")

                    elif cmd == "/verify":
                        if last_server_model_label:
                            console.print(
                                f"[bold]Server confirmed:[/bold] {last_server_model_label} "
                                f"({last_server_model_hash})"
                            )
                        else:
                            console.print(
                                "[dim]No server model info yet. Send a message first.[/dim]"
                            )

                    elif cmd == "/new":
                        svc.reset()
                        history.clear()
                        last_server_model_hash = None
                        last_server_model_label = None
                        console.print("[green]Started new conversation.[/green]")

                    elif cmd == "/save":
                        if not arg:
                            console.print("[red]Usage: /save <filename>[/red]")
                        elif not history:
                            console.print("[yellow]No conversation to save.[/yellow]")
                        else:
                            text = "\n\n".join(
                                f"**You:** {h['prompt']}\n\n**Gemini:** {h['response']}"
                                for h in history
                            )
                            Path(arg).write_text(text)
                            console.print(f"[green]Saved {len(history)} turns to {arg}[/green]")

                    elif cmd == "/history":
                        if not history:
                            console.print("[dim]No conversation history yet.[/dim]")
                        else:
                            for i, h in enumerate(history, 1):
                                console.print(f"[dim]Turn {i}:[/dim] {h['prompt'][:60]}...")

                    else:
                        console.print(f"[red]Unknown command: {cmd}[/red] (try /help)")

                    continue

                # ── Regular chat message ────────────────────────────
                try:
                    response = await svc.send(user_input, model=current_model)

                    # Track server-confirmed model
                    if response.server_model_label:
                        last_server_model_hash = response.server_model_hash
                        last_server_model_label = response.server_model_label

                    if response.text:
                        # Show server-confirmed model tag
                        model_tag = ""
                        if response.server_model_label:
                            model_tag = f"[dim][{response.server_model_label}][/dim] "

                        console.print()
                        if model_tag:
                            console.print(model_tag)
                        console.print(Markdown(response.text))
                        console.print()
                        history.append({
                            "prompt": user_input,
                            "response": response.text,
                        })
                    else:
                        console.print("[yellow]No text in response.[/yellow]\n")

                    if response.has_images:
                        images = response.candidates[0].generated_images
                        if images:
                            count = len(images)
                            console.print(
                                f"[bold magenta]Generated {count} image(s):[/bold magenta]"
                            )
                            for img in images:
                                try:
                                    url = img[0][3][3] if isinstance(img, list) else str(img)
                                    console.print(f"[link={url}]{url}[/link]")
                                except (IndexError, TypeError):
                                    pass
                            console.print()

                except TokenFactoryError as e:
                    _handle_token_factory_error(e, command="chat")
                except GeminiError as e:
                    console.print(f"[red]Error: {e}[/red]\n")

    try:
        run_async(_run())
    except TokenFactoryError as e:
        _handle_token_factory_error(e, command="chat")
    except AuthError as e:
        console.print(f"[red]Auth error: {e}[/red]")
    except GeminiError as e:
        console.print(f"[red]Error: {e}[/red]")


# ── Image ───────────────────────────────────────────────────────────────────


@cli.command()
@click.argument("prompt")
@click.option("--model", "-m", default=None,
              help="Model: flash (Nano Banana 2, default), pro (Nano Banana Pro)")
@click.option("--output", "-o", default=None, help="Save generated image to file")
@click.pass_context
def image(ctx, prompt, model, output):
    """Generate an image from a text prompt.

    Uses Nano Banana 2 (Gemini 3.1 Flash Image) by default. Pass -m pro for
    Nano Banana Pro (Gemini 3 Pro Image) for Pro/Ultra subscribers.
    """
    profile_name = ctx.obj.get("profile")

    async def _image():
        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.image import ImageService

            svc = ImageService(client)
            response = await svc.generate(prompt, model=model)

            if response.has_images:
                images = response.candidates[0].generated_images
                if images and len(images) > 0:
                    url = images[0][0][3][3] if isinstance(images[0], list) else str(images[0])

                    # Determine output path (explicit or temp file)
                    if output:
                        out_path = output
                    else:
                        import tempfile
                        out_path = tempfile.mktemp(suffix=".png", prefix="gemini_")

                    saved = await svc.download(url, out_path)
                    console.print(f"[green]Image saved to {saved}[/green]")

                    # Auto-open image for viewing when no explicit output path
                    if not output:
                        import platform
                        import subprocess as sp
                        system = platform.system()
                        try:
                            if system == "Darwin":
                                sp.Popen(["open", str(saved)])
                            elif system == "Linux":
                                sp.Popen(["xdg-open", str(saved)])
                            elif system == "Windows":
                                sp.Popen(["start", str(saved)], shell=True)
                        except Exception:
                            pass  # Non-fatal if viewer can't open
                else:
                    console.print("[yellow]Image generated but no URL found.[/yellow]")

                # Also show any text response alongside the image
                if response.text:
                    console.print(Markdown(response.text))
            elif response.text:
                console.print(Markdown(response.text))
            else:
                console.print("[yellow]No images generated.[/yellow]")

    try:
        run_async(_image())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


# ── Video ───────────────────────────────────────────────────────────────────


@cli.command()
@click.argument("prompt")
@click.option("--model", "-m", default=None,
              help="Model: pro, flash, thinking (default: flash)")
@click.option("--output", "-o", default=None, help="Save generated video to file (.mp4)")
@click.pass_context
def video(ctx, prompt, model, output):
    """Generate a video from a text prompt (Veo 3.1).

    \b
    Examples:
        gemcli video "A sunset on the beach with waves crashing"
        gemcli video "A cat playing piano" -o cat_piano.mp4
    """
    profile_name = ctx.obj.get("profile")

    async def _video():
        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.video import (
                VideoService,
                extract_video_metadata,
                extract_video_url,
            )

            svc = VideoService(client)

            # Start generation
            response = await svc.generate(prompt, model=model)
            if response.text:
                console.print(Markdown(response.text))

            # Check if video data is already in the initial response
            video_data = None
            if response.has_video:
                for c in response.candidates:
                    if c.generated_video:
                        video_data = c.generated_video
                        break

            # If no video data yet and -o requested, poll for completion
            if not video_data and output:
                if not svc._cid:
                    console.print(
                        "[yellow]Video generation started but no conversation ID "
                        "returned. Check the web UI for progress.[/yellow]"
                    )
                    return

                console.print(
                    "[yellow]Video generation started. "
                    "Waiting for completion (up to 5 minutes)...[/yellow]"
                )
                with console.status(
                    "[bold cyan]Generating video...[/bold cyan]",
                    spinner="dots",
                ):
                    completed = await svc.wait_for_completion(
                        poll_interval=15.0,
                        max_wait=300.0,
                    )

                if completed and completed.has_video:
                    for c in completed.candidates:
                        if c.generated_video:
                            video_data = c.generated_video
                            break
                    if completed.text and completed.text != response.text:
                        console.print(Markdown(completed.text))
                else:
                    console.print(
                        "[red]Video generation timed out or failed. "
                        "Check the web UI for status.[/red]"
                    )
                    return

            # If we have video data, show metadata and optionally download
            if video_data:
                meta = extract_video_metadata(video_data)
                if meta:
                    if meta.get("model"):
                        console.print(f"[bold]Model:[/bold]  {meta['model']}")
                    if meta.get("duration_seconds"):
                        console.print(
                            f"[bold]Duration:[/bold] {meta['duration_seconds']}s"
                        )
                    if meta.get("width") and meta.get("height"):
                        console.print(
                            f"[bold]Resolution:[/bold] "
                            f"{meta['width']}x{meta['height']}"
                        )

                if output:
                    url = extract_video_url(video_data)
                    if not url:
                        console.print(
                            "[red]Could not extract download URL from response.[/red]"
                        )
                        return
                    saved = await svc.download(url, output)
                    console.print(f"\n[green]Video saved to {saved}[/green]")
                else:
                    url = extract_video_url(video_data)
                    if url:
                        console.print(
                            f"\n[dim]Download URL: {url[:80]}...[/dim]"
                        )
                    console.print(
                        "\n[dim]Use -o <file> to download "
                        "(e.g., gemcli video \"...\" -o video.mp4)[/dim]"
                    )
            elif not output:
                console.print(
                    "[yellow]Video generation started. "
                    "Use the web UI to check progress, "
                    "or use -o <file> to auto-download.[/yellow]"
                )

    try:
        run_async(_video())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


# ── Music ──────────────────────────────────────────────────────────────────


@cli.command()
@click.argument("prompt", required=False, default=None)
@click.option("--model", "-m", default=None,
              help="Model: pro, flash, thinking (default: flash)")
@click.option("--output", "-o", default=None,
              help="Save to file (.mp3 for audio, .mp4 for video)")
@click.option("--format", "-f", "fmt", default=None,
              type=click.Choice(["audio", "video"]),
              help="Download format: audio (MP3) or video (MP4 with cover art)")
@click.option("--style", "-s", default=None,
              help="Music style preset (e.g., 8-bit, k-pop, emo). Use --list-styles to see all.")
@click.option("--list-styles", is_flag=True, help="List available music style presets and exit")
@click.pass_context
def music(ctx, prompt, model, output, fmt, style, list_styles):
    """Generate a music track from a text prompt (Lyria 3).

    \b
    Examples:
        gemcli music "A comical R&B slow jam about a sock"
        gemcli music "Epic orchestral piece" -o track.mp3
        gemcli music "Cats playing video games" -s 8-bit
        gemcli music "Summer vibes" -s k-pop -o track.mp3
    \b
    Format is auto-detected from file extension when using -o:
        .mp3 → audio    .mp4 → video
    Without -o, displays track info only (use -o to download).
    \b
    Use --list-styles to see available style presets.
    """
    if list_styles:
        from gemini_web_mcp_cli.core.music_presets import MUSIC_PRESETS

        console.print("[bold]Available music style presets:[/bold]\n")
        for key, preset in sorted(MUSIC_PRESETS.items()):
            console.print(f"  [bold]{key}[/bold]  ({preset['name']})")
        console.print("\n[dim]Use with: gemcli music \"prompt\" -s <style>[/dim]")
        return

    if not prompt:
        console.print("[red]Error: Missing argument 'PROMPT'.[/red]")
        console.print("[dim]Usage: gemcli music \"your prompt here\"[/dim]")
        raise SystemExit(1)

    profile_name = ctx.obj.get("profile")

    async def _music():
        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.music import MusicFormat, MusicService

            svc = MusicService(client)
            if style:
                console.print(f"[bold]Generating music track (style: {style})...[/bold]")
            else:
                console.print("[bold]Generating music track...[/bold]")
            response = await svc.generate(prompt, model=model, style=style)

            if not response.has_music:
                if response.text:
                    console.print(Markdown(response.text))
                else:
                    console.print("[yellow]No music generated.[/yellow]")
                return

            music_data = response.candidates[0].generated_music
            if not music_data:
                console.print("[yellow]No music data in response.[/yellow]")
                return

            # Extract track metadata (entry 2)
            metadata = music_data[2] if len(music_data) > 2 and music_data[2] else None
            if metadata and isinstance(metadata, list):
                title = metadata[0] if len(metadata) > 0 else "Unknown"
                album = metadata[2] if len(metadata) > 2 else ""
                genre = metadata[4] if len(metadata) > 4 else ""
                moods = metadata[5] if len(metadata) > 5 else []
                console.print(f"\n[bold]Track:[/bold]  {title}")
                if album:
                    console.print(f"[bold]Album:[/bold]  {album}")
                if genre:
                    console.print(f"[bold]Genre:[/bold]  {genre}")
                if moods and isinstance(moods, list):
                    console.print(f"[bold]Mood:[/bold]   {', '.join(str(m) for m in moods)}")

            # Show text response
            if response.text:
                console.print()
                console.print(Markdown(response.text))

            # Download if output path specified
            if output:
                # Auto-detect format from extension if not explicitly set
                download_fmt = fmt
                if not download_fmt:
                    ext = Path(output).suffix.lower()
                    if ext == ".mp4":
                        download_fmt = "video"
                    else:
                        download_fmt = "audio"  # Default to audio

                music_fmt = MusicFormat(download_fmt)

                # Extract download URL
                try:
                    if music_fmt == MusicFormat.VIDEO:
                        url = music_data[1][1][7][1]  # MP4 video URL
                    else:
                        url = music_data[0][1][7][1]  # MP3 audio URL
                except (IndexError, TypeError):
                    console.print("[red]Could not extract download URL from response.[/red]")
                    return

                saved = await svc.download(url, output, fmt=music_fmt)
                console.print(f"\n[green]Track saved to {saved}[/green]")
            else:
                console.print(
                    "\n[dim]Use -o <file> to download "
                    "(e.g., gemcli music \"...\" -o track.mp3)[/dim]"
                )

    try:
        run_async(_music())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


# ── Research ────────────────────────────────────────────────────────────────


@cli.command()
@click.argument("query")
@click.option("--model", "-m", default=None,
              help="Model: pro, flash, thinking (default: flash)")
@click.option("--output", "-o", default=None, help="Save research report to file")
@click.pass_context
def research(ctx, query, model, output):
    """Start a Deep Research query."""
    profile_name = ctx.obj.get("profile")

    async def _research():
        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.research import ResearchService

            svc = ResearchService(client)
            console.print("[bold]Creating research plan...[/bold]")
            plan = await svc.create_plan(query, model=model)
            if plan.text:
                console.print(Markdown(plan.text))

    try:
        run_async(_research())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


# ── Limits ──────────────────────────────────────────────────────────────────


@cli.command()
@click.pass_context
def limits(ctx):
    """Show current usage limits and remaining quota.

    Requires Chrome for browser cookie authentication (Token Factory).
    Queries the qpEbW RPC to get per-feature usage data including
    current usage, daily limit, remaining quota, and reset time.

    Examples:
        gemcli limits
    """
    profile_name = ctx.obj.get("profile")

    async def _limits():
        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.limits import LimitsService

            svc = LimitsService(client)
            feature_limits = await svc.get_limits()
            if not feature_limits:
                console.print(
                    "[yellow]Could not retrieve limits. "
                    "Browser cookies required (Token Factory).[/yellow]"
                )
                return

            console.print()
            console.print("[bold]Gemini Usage Limits[/bold]")
            console.print()
            header = (
                f"{'Feature':<22s} {'Used':>5s} {'Limit':>6s} "
                f"{'Left':>5s}  {'Resets In':>10s}"
            )
            console.print(f"[dim]{header}[/dim]")
            console.print(f"[dim]{'─' * 54}[/dim]")

            for fl in feature_limits:
                if fl.limit == 0:
                    continue
                secs = fl.reset_in_seconds
                if secs >= 3600:
                    reset_str = f"{secs / 3600:.1f}h"
                else:
                    reset_str = f"{secs / 60:.0f}m"

                if fl.is_exhausted:
                    style = "red bold"
                elif fl.remaining <= fl.limit * 0.2:
                    style = "yellow"
                else:
                    style = "green"

                line = (
                    f"{fl.name:<22s} {fl.used:>5d} {fl.limit:>6d} "
                    f"{fl.remaining:>5d}  {reset_str:>10s}"
                )
                console.print(f"[{style}]{line}[/{style}]")

            console.print()

    try:
        run_async(_limits())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


# ── Gems ────────────────────────────────────────────────────────────────────


@cli.group()
def gems():
    """Manage Gemini Gems (custom system prompts)."""
    pass


@gems.command("list")
@click.option("--predefined", is_flag=True, help="Show Google's premade gems")
@click.option("--shared", is_flag=True, help="Show gems shared with you")
@click.pass_context
def gems_list(ctx, predefined, shared):
    """List available gems."""
    profile_name = ctx.obj.get("profile")

    async def _list():
        async with GeminiClient(profile_name=profile_name) as client:
            # Gems RPCs require browser cookies (Token Factory)
            await client.send(prompt=".", force_token_factory=True)

            from gemini_web_mcp_cli.services.gems import GemsService

            svc = GemsService(client)
            gem_list = await svc.list_gems(predefined=predefined, shared=shared)
            if not gem_list:
                console.print("No gems found.")
                return
            for g in gem_list:
                console.print(f"  [bold]{g.name}[/bold] ({g.id})")
                if g.description:
                    console.print(f"    {g.description}")
                if g.default_tool:
                    console.print(f"    [dim]Default tool:[/dim] {g.default_tool_name}")
                if g.resources:
                    console.print(f"    [dim]Resources ({len(g.resources)}):[/dim]")
                    for r in g.resources:
                        tag = f"[cyan]{r.source}[/cyan]"
                        detail = r.mime_type if r.source != "notebook" else r.notebook_id
                        console.print(f"      - {r.name} ({detail}) [{tag}]")

    try:
        run_async(_list())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


@gems.command("create")
@click.option("--name", required=True, help="Gem name")
@click.option("--description", default="", help="Gem description")
@click.option("--prompt", required=True, help="System prompt")
@click.option("--notebook", multiple=True, help="NotebookLM notebook ID to attach (repeatable)")
@click.pass_context
def gems_create(ctx, name, description, prompt, notebook):
    """Create a new custom gem."""
    profile_name = ctx.obj.get("profile")

    async def _create():
        async with GeminiClient(profile_name=profile_name) as client:
            # Gems RPCs require browser cookies (Token Factory)
            await client.send(prompt=".", force_token_factory=True)

            from gemini_web_mcp_cli.services.gems import GemsService

            svc = GemsService(client)
            await svc.create(
                name, description, prompt,
                notebook_ids=list(notebook) if notebook else None,
            )
            console.print(f"[green]Gem '{name}' created.[/green]")

    try:
        run_async(_create())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


@gems.command("delete")
@click.argument("gem_id")
@click.confirmation_option(prompt="Are you sure?")
@click.pass_context
def gems_delete(ctx, gem_id):
    """Delete a custom gem."""
    profile_name = ctx.obj.get("profile")

    async def _delete():
        async with GeminiClient(profile_name=profile_name) as client:
            # Gems RPCs require browser cookies (Token Factory)
            await client.send(prompt=".", force_token_factory=True)

            from gemini_web_mcp_cli.services.gems import GemsService

            svc = GemsService(client)
            await svc.delete(gem_id)
            console.print(f"[green]Gem '{gem_id}' deleted.[/green]")

    try:
        run_async(_delete())
    except TokenFactoryError as e:
        _handle_token_factory_error(e)
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


# ── File ────────────────────────────────────────────────────────────────────


@cli.group()
def file():
    """Manage file uploads for conversation context."""
    pass


@file.command("upload")
@click.argument("file_path", type=click.Path(exists=True))
@click.pass_context
def file_upload(ctx, file_path):
    """Upload a file to Gemini and get its identifier."""
    profile_name = ctx.obj.get("profile")

    async def _upload():
        async with GeminiClient(profile_name=profile_name) as client:
            from gemini_web_mcp_cli.services.file import FileService

            svc = FileService(client)
            file_id = await svc.upload(file_path)
            console.print("[green]File uploaded successfully![/green]")
            console.print(f"[bold]Identifier:[/bold] {file_id}")
            console.print("\n[dim]You can use this identifier in raw stream_generate calls.[/dim]")

    try:
        run_async(_upload())
    except (AuthError, GeminiError) as e:
        console.print(f"[red]Error: {e}[/red]")


# ── Config ──────────────────────────────────────────────────────────────────


@cli.group()
def config():
    """Manage configuration settings."""
    pass


@config.command("show")
def config_show():
    """Show current configuration."""

    pm = ProfileManager()
    active = pm.get_active_profile()
    profiles = pm.list_profiles()
    has_nlm = pm.has_nlm_profiles()
    cfg = pm._read_config()
    default_model = cfg.get("default_model", "flash-preview")

    console.print(f"[bold]Active profile:[/bold]  {active}")
    console.print(f"[bold]Default model:[/bold]   {default_model}")
    console.print(f"[bold]Profiles:[/bold]        {', '.join(profiles) if profiles else 'none'}")
    console.print(f"[bold]NLM available:[/bold]   {'yes' if has_nlm else 'no'}")
    console.print(f"[bold]Config dir:[/bold]      {pm.config_dir}")
    console.print(f"[bold]Version:[/bold]         {__version__}")
    console.print()
    console.print("[dim]Model shortcuts: pro, flash, thinking, preview[/dim]")
    console.print("[dim]Set default:     gemcli config set model pro[/dim]")


@config.command("get")
@click.argument("key")
def config_get(key):
    """Get a specific configuration value.

    Keys: active_profile, model (default model)
    """
    pm = ProfileManager()
    config_data = pm._read_config()

    if key == "active_profile":
        console.print(pm.get_active_profile())
    elif key == "model":
        console.print(config_data.get("default_model", "flash-preview"))
    elif key in config_data:
        console.print(str(config_data[key]))
    else:
        console.print(f"[red]Unknown config key: {key}[/red]")
        console.print("Available: active_profile, model")


@config.command("set")
@click.argument("key")
@click.argument("value")
def config_set(key, value):
    """Set a configuration value.

    Examples:
        gemcli config set model pro
        gemcli config set model thinking
        gemcli config set model flash
        gemcli config set active_profile work
    """
    pm = ProfileManager()

    if key == "active_profile":
        try:
            pm.set_active_profile(value)
            console.print(f"[green]Set {key} = {value}[/green]")
        except GeminiError as e:
            console.print(f"[red]{e}[/red]")
    elif key == "model":
        from gemini_web_mcp_cli.core.constants import get_model
        try:
            model = get_model(value)
            config_data = pm._read_config()
            config_data["default_model"] = value
            pm._write_config(config_data)
            console.print(f"[green]Default model set to: {value}[/green] ({model.name})")
        except ValueError as e:
            console.print(f"[red]{e}[/red]")
    else:
        config_data = pm._read_config()
        config_data[key] = value
        pm._write_config(config_data)
        console.print(f"[green]Set {key} = {value}[/green]")


# ── Chrome Daemon ──────────────────────────────────────────────────────────


def _format_uptime(total_seconds: int) -> str:
    """Format seconds as human-readable uptime."""
    days, remainder = divmod(total_seconds, 86400)
    hours, remainder = divmod(remainder, 3600)
    minutes, _ = divmod(remainder, 60)
    parts = []
    if days:
        parts.append(f"{days}d")
    if hours:
        parts.append(f"{hours}h")
    parts.append(f"{minutes}m")
    return " ".join(parts)


@cli.group()
def chrome():
    """Manage persistent Chrome for Token Factory."""
    pass


@chrome.command("start")
@click.option("--visible", is_flag=True, help="Show Chrome window (default: headless)")
@click.option("--port", "-p", type=int, default=None, help="CDP debugging port (default: auto)")
@click.pass_context
def chrome_start(ctx, visible, port):
    """Start a persistent Chrome instance for Token Factory.

    Chrome runs in the background and survives terminal close.
    Token Factory will automatically detect and use it.

    Must be run from an interactive terminal (macOS security).

    \b
    Examples:
        gemcli chrome start              # Start headless
        gemcli chrome start --visible    # Start with window
        gemcli chrome start -p 9222      # Use specific port
    """
    from gemini_web_mcp_cli.core.chrome_manager import start_chrome_daemon

    pm = ProfileManager()
    profile_name = ctx.obj.get("profile") or pm.get_active_profile()
    chrome_profile_path = pm.get_chrome_profile_path(profile_name)

    if not chrome_profile_path:
        console.print("[red]No Chrome profile found.[/red]")
        console.print("Run [bold]gemcli login[/bold] first to set up authentication.")
        raise SystemExit(1)

    try:
        info = start_chrome_daemon(
            profile_name=profile_name,
            chrome_profile_path=chrome_profile_path,
            headless=not visible,
            port=port,
        )
        console.print("[green]Chrome daemon started.[/green]")
        console.print(f"  [bold]PID:[/bold]     {info.pid}")
        console.print(f"  [bold]Port:[/bold]    {info.port}")
        console.print(f"  [bold]Profile:[/bold] {info.profile_name}")
        console.print(f"  [bold]Mode:[/bold]    {'headless' if info.headless else 'visible'}")
        console.print()
        console.print("[dim]Token Factory will automatically detect this Chrome instance.[/dim]")
        console.print("[dim]Stop with: gemcli chrome stop[/dim]")
        console.print(
            "[dim]Note: After a reboot, run this command again to restart the daemon.[/dim]"
        )
    except TokenFactoryError as e:
        console.print(f"[red]{e}[/red]")
        raise SystemExit(1)


@chrome.command("stop")
def chrome_stop():
    """Stop the persistent Chrome instance."""
    from gemini_web_mcp_cli.core.chrome_manager import stop_chrome_daemon

    stopped = stop_chrome_daemon()
    if stopped:
        console.print("[green]Chrome daemon stopped.[/green]")
    else:
        console.print("[yellow]No Chrome daemon is running.[/yellow]")


@chrome.command("status")
def chrome_status():
    """Show persistent Chrome status."""
    import datetime

    from gemini_web_mcp_cli.core.chrome_manager import (
        check_daemon_health,
        is_gemini_authenticated,
    )

    info, status = check_daemon_health()

    if status == "not_running":
        console.print("[dim]Chrome daemon is not running.[/dim]")
        console.print("[dim]Start with: gemcli chrome start[/dim]")
        return

    if status == "stale":
        console.print("[yellow]Chrome daemon was running but has exited.[/yellow]")
        console.print("[dim]Cleaned up stale state. Start fresh with: gemcli chrome start[/dim]")
        return

    # status is "running" or "unresponsive"
    started = datetime.datetime.fromtimestamp(info.started_at)
    uptime = datetime.datetime.now() - started
    uptime_str = _format_uptime(int(uptime.total_seconds()))

    console.print("  [bold]Status:[/bold]  ", end="")
    if status == "running":
        console.print("[green]running[/green]")
    else:
        console.print("[yellow]unresponsive[/yellow]")

    console.print(f"  [bold]PID:[/bold]     {info.pid}")
    console.print(f"  [bold]Port:[/bold]    {info.port}")
    console.print(f"  [bold]Profile:[/bold] {info.profile_name}")
    console.print(f"  [bold]Mode:[/bold]    {'headless' if info.headless else 'visible'}")
    console.print(f"  [bold]Uptime:[/bold]  {uptime_str}")

    if status == "running":
        authenticated = is_gemini_authenticated(info.port)
        if authenticated:
            console.print("  [bold]Auth:[/bold]   [green]authenticated[/green]")
        else:
            console.print("  [bold]Auth:[/bold]   [yellow]not authenticated to Gemini[/yellow]")
            console.print("       [yellow]→[/yellow] Run: gemcli login")

    if status == "unresponsive":
        console.print()
        console.print("[yellow]Chrome is running but not responding to CDP.[/yellow]")
        console.print("[dim]Try: gemcli chrome stop && gemcli chrome start[/dim]")


# ── Doctor ──────────────────────────────────────────────────────────────────


def _find_chrome_binary() -> str | None:
    """Find Chrome binary path, platform-aware."""
    system = platform.system()
    if system == "Darwin":
        mac_path = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
        if Path(mac_path).exists():
            return mac_path
    elif system == "Windows":
        for env_var in ("PROGRAMFILES", "LOCALAPPDATA"):
            base = os.environ.get(env_var, "")
            if base:
                win_path = Path(base) / "Google" / "Chrome" / "Application" / "chrome.exe"
                if win_path.exists():
                    return str(win_path)
    return (
        shutil.which("google-chrome")
        or shutil.which("chrome")
        or shutil.which("chromium")
    )


@cli.command()
@click.option("--verbose", "-v", is_flag=True, help="Show additional details")
def doctor(verbose):
    """Diagnose installation and configuration."""
    console.print(f"\n[bold]gemcli doctor[/bold] (v{__version__})\n")
    all_ok = True

    # ── Verbose: platform info ──
    if verbose:
        console.print(f"  [dim]Python:   {sys.version.split()[0]}[/dim]")
        console.print(f"  [dim]Platform: {platform.platform()}[/dim]")
        console.print(f"  [dim]Version:  {__version__}[/dim]")
        console.print()

    # ── 1. Installation ──
    console.print("  [bold]Installation[/bold]")
    gemcli_path = shutil.which("gemcli")
    if gemcli_path:
        console.print(f"  [green]✓[/green]  gemcli binary: {gemcli_path}")
    else:
        console.print("  [yellow]-[/yellow]  gemcli binary: not on PATH (running via python -m)")

    mcp_path = shutil.which("gemini-web-mcp")
    if mcp_path:
        console.print(f"  [green]✓[/green]  gemini-web-mcp binary: {mcp_path}")
    else:
        console.print("  [red]✗[/red]  gemini-web-mcp binary: not found on PATH")
        console.print("       [yellow]→[/yellow] Fix: pip install gemini-web-mcp-cli[mcp]")
        all_ok = False

    # ── 2. Chrome ──
    console.print()
    console.print("  [bold]Chrome[/bold]")
    chrome_path = _find_chrome_binary()
    if chrome_path:
        console.print("  [green]✓[/green]  Chrome binary: found")
        if verbose:
            console.print(f"       [dim]{chrome_path}[/dim]")
    else:
        console.print("  [yellow]-[/yellow]  Chrome binary: not found")
        console.print("       [yellow]→[/yellow] Needed for login and Token Factory (Pro/Thinking)")

    pm = ProfileManager()
    chrome_profiles_dir = pm.config_dir / "chrome-profiles"
    if chrome_profiles_dir.exists():
        chrome_profiles = [p for p in chrome_profiles_dir.iterdir() if p.is_dir()]
        if chrome_profiles:
            names = ", ".join(p.name for p in chrome_profiles)
            console.print(f"  [green]✓[/green]  Chrome profiles: {names}")
        else:
            console.print("  [dim]-[/dim]  Chrome profiles: directory exists but empty")
    else:
        console.print("  [dim]-[/dim]  Chrome profiles: none saved yet")

    # Chrome daemon status
    from gemini_web_mcp_cli.core.chrome_manager import check_daemon_health

    daemon_info, daemon_status = check_daemon_health()
    if daemon_status == "running":
        console.print(
            f"  [green]✓[/green]  Chrome daemon: running "
            f"(port {daemon_info.port}, PID {daemon_info.pid})"
        )
        if verbose:
            import datetime

            started = datetime.datetime.fromtimestamp(daemon_info.started_at)
            uptime = datetime.datetime.now() - started
            console.print(f"       [dim]Profile: {daemon_info.profile_name}[/dim]")
            console.print(
                f"       [dim]Mode: {'headless' if daemon_info.headless else 'visible'}[/dim]"
            )
            uptime_str = _format_uptime(int(uptime.total_seconds()))
            console.print(f"       [dim]Uptime: {uptime_str}[/dim]")
    elif daemon_status == "unresponsive":
        console.print(
            f"  [yellow]-[/yellow]  Chrome daemon: unresponsive (PID {daemon_info.pid})"
        )
        console.print(
            "       [yellow]→[/yellow] Try: gemcli chrome stop && gemcli chrome start"
        )
        all_ok = False
    elif daemon_status == "stale":
        console.print("  [dim]-[/dim]  Chrome daemon: stale (auto-cleaned)")
    else:
        console.print("  [dim]-[/dim]  Chrome daemon: not running")
        if verbose:
            console.print("       [dim]Start with: gemcli chrome start[/dim]")

    # ── 3. Configuration ──
    console.print()
    console.print("  [bold]Configuration[/bold]")
    if pm.config_dir.exists():
        console.print(f"  [green]✓[/green]  Config dir: {pm.config_dir}")
    else:
        console.print("  [dim]-[/dim]  Config dir: not created yet")
        console.print("       [yellow]→[/yellow] Run: gemcli login")

    profiles = pm.list_profiles()
    if profiles:
        active = pm.get_active_profile()
        console.print(f"  [green]✓[/green]  Profiles: {', '.join(profiles)} (active: {active})")
    else:
        console.print("  [yellow]-[/yellow]  No profiles found")

    if pm.has_nlm_profiles():
        nlm = pm._list_nlm_profiles()
        console.print(f"  [green]✓[/green]  NotebookLM MCP: {', '.join(nlm)}")
    else:
        console.print("  [dim]-[/dim]  NotebookLM MCP: not installed")

    # ── 4. Authentication ──
    console.print()
    console.print("  [bold]Authentication[/bold]")
    active_profile = pm.get_active_profile()
    auth_data = pm.load_auth(active_profile)
    if auth_data and auth_data.get("cookies", {}).get("__Secure-1PSID"):
        console.print(f"  [green]✓[/green]  Auth ({active_profile}): cookies present")
        if verbose:
            cookie_count = len(auth_data.get("cookies", {}))
            console.print(f"       [dim]{cookie_count} cookies stored[/dim]")
            last_refreshed = auth_data.get("last_refreshed")
            if last_refreshed:
                import datetime

                ts = datetime.datetime.fromtimestamp(last_refreshed)
                console.print(f"       [dim]Last refreshed: {ts.isoformat()}[/dim]")
        if auth_data.get("tokens", {}).get("snlm0e"):
            console.print(f"  [green]✓[/green]  Auth ({active_profile}): tokens present")
        else:
            console.print(f"  [yellow]-[/yellow]  Auth ({active_profile}): no tokens")
            console.print("       [yellow]→[/yellow] Run: gemcli login")
            all_ok = False
    else:
        console.print(f"  [yellow]-[/yellow]  Auth ({active_profile}): not authenticated")
        if pm.profile_exists(active_profile):
            source = pm.get_profile_source(active_profile)
            if source == "nlm":
                console.print(
                    "       [yellow]→[/yellow] Profile from NLM — run gemcli login to add "
                    "Gemini cookies"
                )
        else:
            console.print("       [yellow]→[/yellow] Run: gemcli login")

    # ── 5. MCP Clients ──
    console.print()
    console.print("  [bold]MCP Clients[/bold]")
    from gemini_web_mcp_cli.setup import (
        CLIENT_REGISTRY,
        MCP_SERVER_KEY,
        _is_configured,
        _read_json_config,
    )

    configured_clients = []
    for cid, info in CLIENT_REGISTRY.items():
        if cid == "claude-code":
            # Check via subprocess
            claude_cmd = shutil.which("claude")
            if claude_cmd:
                try:
                    result = subprocess.run(
                        [claude_cmd, "mcp", "list"],
                        capture_output=True,
                        text=True,
                        timeout=5,
                    )
                    if MCP_SERVER_KEY.lower() in result.stdout.lower():
                        configured_clients.append("Claude Code")
                except (subprocess.TimeoutExpired, OSError):
                    pass
            continue
        if cid == "codex":
            continue  # Skill-based, no MCP config
        config_fn = info.get("config_fn")
        if config_fn:
            key = info.get("key", MCP_SERVER_KEY)
            path = config_fn()
            cfg = _read_json_config(path)
            if _is_configured(cfg, key):
                configured_clients.append(info["name"])

    if configured_clients:
        console.print(f"  [green]✓[/green]  Configured: {', '.join(configured_clients)}")
    else:
        console.print("  [dim]-[/dim]  No MCP clients configured")
        console.print("       [yellow]→[/yellow] Run: gemcli setup add <client>")

    # ── 6. Skills ──
    console.print()
    console.print("  [bold]Skills[/bold]")
    from gemini_web_mcp_cli.skill import TOOL_CONFIGS as SKILL_CONFIGS
    from gemini_web_mcp_cli.skill import check_install_status as skill_check

    installed_skills = []
    for tool_name in SKILL_CONFIGS:
        if tool_name == "other":
            continue
        is_installed, _ = skill_check(tool_name, "user")
        if is_installed:
            installed_skills.append(tool_name)

    if installed_skills:
        console.print(f"  [green]✓[/green]  Installed: {', '.join(installed_skills)}")
    else:
        console.print("  [dim]-[/dim]  No skills installed")
        console.print("       [yellow]→[/yellow] Run: gemcli skill install <tool>")

    # ── Summary ──
    console.print()
    if all_ok:
        console.print("  [green]All checks passed.[/green]\n")
    else:
        console.print("  [yellow]Some issues found. See above.[/yellow]\n")


# ── Setup (MCP configuration for AI tools) ─────────────────────────────────

from gemini_web_mcp_cli.setup import setup  # noqa: E402

cli.add_command(setup)

# ── Skill (install AI tool skills) ─────────────────────────────────────────

from gemini_web_mcp_cli.skill import skill  # noqa: E402

cli.add_command(skill)


# ── Hack (launch AI tools connected to Gemini) ────────────────────────────


@cli.group()
def hack():
    """Launch AI tools connected to Gemini models."""
    pass


@hack.command(
    "claude",
    context_settings={
        "allow_extra_args": True,
        "allow_interspersed_args": False,
    },
)
@click.option(
    "--model", "-m",
    type=click.Choice(["flash", "pro", "thinking"], case_sensitive=False),
    default=None,
    help="Gemini model to use (default: flash)",
)
@click.pass_context
def hack_claude(ctx, model):
    """Launch Claude Code using Gemini as the backend.

    Starts a local Anthropic-compatible API server backed by Gemini,
    then launches Claude Code connected to it. All remaining arguments
    are passed through to Claude Code.

    \b
    Examples:
        gemcli hack claude
        gemcli hack claude -m pro
        gemcli hack claude --model thinking
        gemcli hack claude -m pro -p "fix this bug"
    """
    from gemini_web_mcp_cli.hack import cmd_hack_claude

    raise SystemExit(cmd_hack_claude(ctx.args, model=model))


# ── Verb-first aliases ──────────────────────────────────────────────────────
# These let users write "gemcli list gems" instead of "gemcli gems list"


@cli.group(name="list")
def verb_list():
    """List resources (gems, profiles, skills)."""
    pass


@verb_list.command("gems")
@click.option("--predefined", is_flag=True, help="Show Google's premade gems")
@click.option("--shared", is_flag=True, help="Show gems shared with you")
@click.pass_context
def verb_list_gems(ctx, predefined, shared):
    """List available gems."""
    ctx.invoke(gems_list, predefined=predefined, shared=shared)


@verb_list.command("profiles")
def verb_list_profiles():
    """List all profiles."""
    profile_list()


@verb_list.command("skills")
@click.pass_context
def verb_list_skills(ctx):
    """List skill installation status."""
    from gemini_web_mcp_cli.skill import skill_list

    ctx.invoke(skill_list)


@cli.group(name="install")
def verb_install():
    """Install resources (skills)."""
    pass


@verb_install.command("skill")
@click.argument("tool")
@click.option(
    "--level", "-l", type=click.Choice(["user", "project"]),
    default="user", help="Install at user or project level",
)
@click.pass_context
def verb_install_skill(ctx, tool, level):
    """Install Gemini skill for an AI tool."""
    from gemini_web_mcp_cli.skill import skill_install

    ctx.invoke(skill_install, tool=tool, level=level)


@cli.group(name="update")
def verb_update():
    """Update resources (skills)."""
    pass


@verb_update.command("skill")
@click.argument("tool", required=False, default=None)
@click.pass_context
def verb_update_skill(ctx, tool):
    """Update outdated skills."""
    from gemini_web_mcp_cli.skill import skill_update

    ctx.invoke(skill_update, tool=tool)


@cli.group(name="create")
def verb_create():
    """Create resources (gems, profiles)."""
    pass


@verb_create.command("gem")
@click.option("--name", required=True, help="Gem name")
@click.option("--description", default="", help="Gem description")
@click.option("--prompt", required=True, help="System prompt")
@click.pass_context
def verb_create_gem(ctx, name, description, prompt):
    """Create a new custom gem."""
    ctx.invoke(gems_create, name=name, description=description, prompt=prompt)


@verb_create.command("profile")
@click.argument("name")
def verb_create_profile(name):
    """Create a new profile."""
    pm = ProfileManager()
    try:
        pm.create_profile(name)
        console.print(f"[green]Created profile: {name}[/green]")
    except GeminiError as e:
        console.print(f"[red]{e}[/red]")


@cli.group(name="delete")
def verb_delete():
    """Delete resources (gems, profiles)."""
    pass


@verb_delete.command("gem")
@click.argument("gem_id")
@click.confirmation_option(prompt="Are you sure?")
@click.pass_context
def verb_delete_gem(ctx, gem_id):
    """Delete a custom gem."""
    ctx.invoke(gems_delete, gem_id=gem_id)


@verb_delete.command("profile")
@click.argument("name")
@click.confirmation_option(prompt="Are you sure?")
def verb_delete_profile(name):
    """Delete a profile."""
    pm = ProfileManager()
    try:
        pm.delete_profile(name)
        console.print(f"[green]Deleted profile: {name}[/green]")
    except GeminiError as e:
        console.print(f"[red]{e}[/red]")


@cli.group(name="switch")
def verb_switch():
    """Switch active resources."""
    pass


@verb_switch.command("profile")
@click.argument("name")
def verb_switch_profile(name):
    """Switch the active profile."""
    pm = ProfileManager()
    try:
        pm.set_active_profile(name)
        console.print(f"[green]Switched to profile: {name}[/green]")
    except GeminiError as e:
        console.print(f"[red]{e}[/red]")


@cli.group(name="upload")
def verb_upload():
    """Upload resources (files)."""
    pass


@verb_upload.command("file")
@click.argument("file_path", type=click.Path(exists=True))
@click.pass_context
def verb_upload_file(ctx, file_path):
    """Upload a file to Gemini."""
    ctx.invoke(file_upload, file_path=file_path)


# ── Main entry point ────────────────────────────────────────────────────────


def main():
    """Entry point for the gemcli command."""
    cli(obj={})


if __name__ == "__main__":
    main()
