"""CLI entry point for OCLAWMA."""

from __future__ import annotations

import asyncio
import os
from pathlib import Path

import click

from oclawma import __version__
from oclawma.cli_ui import (
    ErrorHelper,
    accent,
    bullet,
    confirm_prompt,
    header,
    highlight,
    key_value,
    muted,
    print_error,
    print_info,
    print_success,
    print_warning,
    progress_spinner,
    subheader,
)
from oclawma.completion import completion_cli
from oclawma.config import DEFAULT_CONFIG_PATH, Config, ConfigurationError
from oclawma.config.cli import config_cli
from oclawma.config.utils import (
    create_provider_from_profile,
    create_provider_with_fallback,
    get_model_from_config,
    load_context_budget,
)
from oclawma.context.cli import compact_cli
from oclawma.learning.cli import learning_cli
from oclawma.memory.cli import memory_cli
from oclawma.providers import AuthenticationError, KimiProvider
from oclawma.providers.fallback import FallbackProvider
from oclawma.providers.ollama import OllamaProvider
from oclawma.safety import SafetyGuard
from oclawma.safety_cli import safety_cli
from oclawma.self_improvement.cli import self_improvement_cli
from oclawma.session.runner import InteractiveSession
from oclawma.skills.cli import skill_cli, skills_cli
from oclawma.subagent.cli import spawn_cli
from oclawma.tools import create_default_tools_with_safety

# Shell completion setup - Click 8.x uses shell_complete
# The completion is automatically handled by Click when _OCLAWMA_COMPLETE env var is set


def create_provider(
    provider_type: str = "auto",
    base_url: str | None = None,
    model: str | None = None,
    config: Config | None = None,
) -> KimiProvider | OllamaProvider | FallbackProvider:
    """Create the appropriate provider based on configuration.

    Args:
        provider_type: Type of provider to create (ollama, kimi, auto, config).
        base_url: Optional base URL for the provider.
        model: Optional model name for context limit detection.
        config: Optional loaded configuration.

    Returns:
        Configured provider instance.

    Raises:
        AuthenticationError: If required API keys are missing.
        click.UsageError: If provider configuration is invalid.
    """
    # If using config-based provider selection
    if provider_type == "config" and config:
        try:
            if config.fallback.enabled:
                return create_provider_with_fallback(config)
            else:
                return create_provider_from_profile(config)
        except ConfigurationError as e:
            raise click.UsageError(str(e)) from e

    # Legacy provider creation (for backwards compatibility)
    if provider_type == "ollama":
        try:
            return OllamaProvider(base_url=base_url or "http://localhost:11434")
        except Exception:
            raise click.UsageError(
                ErrorHelper.connection_failed(
                    base_url or "http://localhost:11434",
                    "Make sure Ollama is running: ollama serve",
                )
            ) from None

    elif provider_type == "kimi":
        try:
            return KimiProvider()
        except AuthenticationError:
            raise click.UsageError(ErrorHelper.api_key_missing("Kimi", "KIMI_API_KEY")) from None

    elif provider_type == "auto":
        # Try to create a fallback provider with Ollama as primary and Kimi as fallback
        try:
            primary = OllamaProvider(base_url=base_url or "http://localhost:11434")
        except Exception as e:
            raise click.UsageError(f"Failed to create Ollama provider: {e}") from e

        # Check if Kimi is available for fallback
        kimi_api_key = os.environ.get("KIMI_API_KEY")
        if kimi_api_key:
            try:
                fallback = KimiProvider()
                return FallbackProvider(primary, fallback)
            except AuthenticationError:
                pass  # Fall through to just using primary

        # No fallback available, just use primary
        return primary

    else:
        raise click.UsageError(f"Unknown provider type: {provider_type}")


@click.group(invoke_without_command=True)
@click.version_option(
    version=__version__,
    prog_name="oclawma",
)
@click.pass_context
def cli(ctx: click.Context) -> None:
    """OCLAWMA - OpenClaw Workflow Management Agent.

    A powerful, extensible AI agent framework with lazy-loaded skills,
    multi-provider LLM support, and intelligent context management.

    \b
    Quick Start:
        oclawma run                    # Start interactive session
        oclawma config init            # Initialize configuration
        oclawma skills search          # Browse available skills

    \b
    For shell completion:
        oclawma --install-completion   # Install shell completion
        oclawma --show-completion      # Show completion script
    """
    if ctx.invoked_subcommand is None:
        click.echo(ctx.get_help())
        return

    ctx.ensure_object(dict)
    # Configuration will be loaded lazily when needed
    ctx.obj["config"] = None


# Enforce help-on-empty behavior consistently across Click/runtime combinations.
cli.invoke_without_command = True
cli.no_args_is_help = False


def get_config(ctx: click.Context) -> Config:
    """Get or load configuration."""
    if ctx.obj.get("config") is None:
        ctx.obj["config"] = Config.ensure_exists()
    return ctx.obj["config"]


# Add completion commands
cli.add_command(completion_cli)

# Add config commands
cli.add_command(config_cli)

# Add spawn commands
cli.add_command(spawn_cli)

# Add compact commands
cli.add_command(compact_cli)

# Add safety commands
cli.add_command(safety_cli)

# Add learning commands
cli.add_command(learning_cli)

# Add memory commands
cli.add_command(memory_cli)

# Add self-improvement commands
cli.add_command(self_improvement_cli)

# Add skills marketplace commands
cli.add_command(skills_cli)
cli.add_command(skill_cli)


@cli.command()
def version() -> None:
    """Show the version and exit."""
    click.echo(f"{accent('OCLAWMA', bold=True)} version {highlight(__version__)}")


@cli.command()
@click.option(
    "--model",
    help="Model to use for the session (overrides config)",
    shell_complete=lambda ctx, param, incomplete: (
        [
            "llama3.2:latest",
            "qwen2.5:3b",
            "kimi-k2-5",
            "claude-3-5-sonnet",
        ]
        if incomplete
        else []
    ),
)
@click.option(
    "--base-url",
    help="Ollama base URL (overrides config)",
)
@click.option(
    "--provider",
    type=click.Choice(["auto", "ollama", "kimi", "config"], case_sensitive=False),
    default="config",
    help="Provider to use (config: use ~/.oclawma/config.yaml)",
    show_default=True,
)
@click.option(
    "--profile",
    help="Profile to use from config (overrides active profile)",
    shell_complete=lambda ctx, param, incomplete: (
        ["local", "cloud", "fallback"] if incomplete else []
    ),
)
@click.option(
    "--config-path",
    type=click.Path(exists=False),
    help="Path to config file (default: ~/.oclawma/config.yaml)",
)
@click.option(
    "--dry-run",
    is_flag=True,
    help="Simulate commands without executing them",
)
@click.option(
    "--no-safety-confirm",
    is_flag=True,
    help="Disable confirmation prompts for risky operations",
)
@click.pass_context
def run(
    ctx: click.Context,
    model: str | None,
    base_url: str | None,
    provider: str,
    profile: str | None,
    config_path: str | None,
    dry_run: bool,
    no_safety_confirm: bool,
) -> None:
    """Start an interactive session with OCLAWMA.

    This starts a REPL-style interactive session where you can
    chat with the AI assistant and use tools to accomplish tasks.

    Examples:
        oclawma run                          # Use active profile from config
        oclawma run --profile cloud          # Use specific profile
        oclawma run --provider kimi          # Use Kimi directly
        oclawma run --model llama3.2:latest  # Override model
        oclawma run --dry-run                # Simulate without executing
    """
    # Load configuration
    cfg_path = Path(config_path) if config_path else DEFAULT_CONFIG_PATH

    with progress_spinner("Loading configuration...") as update:
        try:
            update("Loading configuration...")
            config = Config.load(cfg_path) if cfg_path.exists() else Config.ensure_exists(cfg_path)
        except Exception as e:
            print_warning(f"Could not load config: {e}")
            config = Config()

    ctx.obj["config"] = config

    # Override profile if specified
    if profile:
        try:
            config.set_active_profile(profile)
            print_success(f"Using profile: {profile}")
        except ConfigurationError:
            available = list(config.profiles.keys()) if config.profiles else []
            print_error(ErrorHelper.profile_not_found(profile, available))
            raise click.Abort() from None

    # Load context budget from config
    budget = load_context_budget(config)

    # Create provider
    try:
        with progress_spinner("Initializing provider...") as update:
            update("Initializing provider...")
            if provider == "config":
                provider_instance = create_provider_with_fallback(config)
            else:
                provider_instance = create_provider(
                    provider_type=provider,
                    base_url=base_url,
                    model=model,
                    config=config if provider == "config" else None,
                )
    except click.UsageError:
        raise
    except AuthenticationError:
        print_error(ErrorHelper.api_key_missing("Provider", "API_KEY"))
        raise click.Abort() from None
    except Exception as e:
        print_error(f"Failed to create provider: {e}")
        raise click.Abort() from e

    # Determine effective model
    effective_model = model or get_model_from_config(config)

    # Show provider info
    click.echo()
    click.echo(header("SESSION CONFIGURATION", width=58))
    click.echo()

    if isinstance(provider_instance, FallbackProvider):
        print_success("Using fallback provider (local → cloud)")
        info_data = provider_instance.get_fallback_info()
        click.echo(key_value("Primary", info_data["primary_provider"]))
        click.echo(key_value("Fallback", info_data["fallback_provider"]))
    elif isinstance(provider_instance, KimiProvider):
        print_success("Using Kimi cloud provider")
    elif isinstance(provider_instance, OllamaProvider):
        print_success(f"Using Ollama provider at {provider_instance.base_url}")

    click.echo(key_value("Model", effective_model))

    # Show safety settings
    if dry_run:
        print_info("Dry-run mode: Commands will be simulated but not executed")
    if no_safety_confirm:
        print_warning("Safety confirmations disabled")

    click.echo()

    # Confirm start if safety is disabled
    if (
        no_safety_confirm
        and not dry_run
        and not confirm_prompt("Safety confirmations are disabled. Continue?")
    ):
        print_info("Session cancelled")
        return

    # Create safety guard
    safety_guard = SafetyGuard(
        dry_run=dry_run,
        require_confirmation=not no_safety_confirm,
    )

    # Create tool registry with safety integration
    tool_registry = create_default_tools_with_safety(safety_guard)

    # Create and run interactive session
    session = InteractiveSession(
        provider=provider_instance,
        model=effective_model,
        budget=budget,
        tool_registry=tool_registry,
    )

    try:
        # Run the async session
        asyncio.run(session.run())

        # Print summary
        session.print_summary()

    except KeyboardInterrupt:
        print_warning("\n\nSession interrupted. Goodbye!")
    except Exception as e:
        print_error(f"\nSession error: {e}")
        import traceback

        traceback.print_exc()
        raise click.ClickException(str(e)) from e


@cli.command(name="status")
@click.option(
    "--config-path",
    type=click.Path(exists=False),
    help="Path to config file",
)
@click.pass_context
def status_cmd(ctx: click.Context, config_path: str | None) -> None:
    """Show system status including context budget and config."""
    cfg_path = Path(config_path) if config_path else DEFAULT_CONFIG_PATH

    # Load configuration with progress
    with progress_spinner("Loading configuration..."):
        try:
            config = Config.ensure_exists(cfg_path)
        except Exception as e:
            print_warning(f"Could not load config: {e}")
            config = Config()

        budget = load_context_budget(config)

    click.echo(header("SYSTEM STATUS", width=58))
    click.echo()

    # Config info
    click.echo(subheader("CONFIGURATION"))
    click.echo(key_value("Config file", str(cfg_path)))
    click.echo(key_value("Active profile", config.active_profile))
    profile = config.get_active_profile()
    click.echo(key_value("Provider", profile.provider))
    click.echo(key_value("Model", profile.model))
    click.echo()

    # Budget visualization
    click.echo(subheader("CONTEXT BUDGET"))
    click.echo(budget.visualize())
    click.echo()

    # Detailed stats
    status_info = budget.get_status()
    click.echo(key_value("Total Budget", f"{budget.total_budget:,} tokens"))
    click.echo(key_value("Used", f"{budget.used_tokens:,} tokens"))
    click.echo(key_value("Remaining", f"{budget.remaining_tokens:,} tokens"))
    click.echo(key_value("Usage", f"{budget.usage_percent:.1f}%"))
    click.echo()

    # Threshold indicators
    click.echo(subheader("THRESHOLDS"))
    warning_pct = (budget.warning_threshold / budget.total_budget) * 100
    critical_pct = (budget.critical_threshold / budget.total_budget) * 100
    click.echo(key_value("Warning", f"{budget.warning_threshold:,} tokens ({warning_pct:.0f}%)"))
    click.echo(key_value("Critical", f"{budget.critical_threshold:,} tokens ({critical_pct:.0f}%)"))
    click.echo(key_value("Maximum", f"{budget.total_budget:,} tokens (100%)"))
    click.echo()

    # Current status message
    if status_info.warning_message:
        click.echo(subheader("STATUS"))
        if status_info.is_critical:
            print_warning(status_info.warning_message)
        elif status_info.is_warning:
            print_info(status_info.warning_message)
        else:
            print_success(status_info.warning_message)
        click.echo()

    # Tips
    click.echo(subheader("COMMANDS"))
    click.echo(f"  {bullet('/compact  - Summarize conversation to free tokens')}")
    click.echo(f"  {bullet('/clear    - Clear attachments and non-essential context')}")
    click.echo(f"  {bullet('/reset    - Start fresh session')}")
    click.echo()
    click.echo(f"  {muted('oclawma config show    - View full configuration')}")
    click.echo(f"  {muted('oclawma config edit    - Edit configuration file')}")
    click.echo(f"  {muted('oclawma safety audit   - View safety audit log')}")


@cli.command(name="doctor")
@click.option(
    "--fix",
    is_flag=True,
    help="Attempt to fix common issues automatically",
)
def doctor_cmd(fix: bool) -> None:
    """Check system health and diagnose common issues.

    Performs various checks to ensure OCLAWMA is properly configured
    and can connect to required services.
    """

    click.echo(header("SYSTEM DOCTOR", width=58))
    click.echo()

    issues_found = 0
    fixes_applied = 0

    # Check Python version
    click.echo(subheader("Python Environment"))
    import sys

    version = sys.version_info
    if version.major == 3 and version.minor >= 9:
        print_success(f"Python {version.major}.{version.minor}.{version.micro}")
    else:
        print_warning(f"Python {version.major}.{version.minor}.{version.micro} (3.9+ recommended)")
        issues_found += 1

    # Check config file
    click.echo()
    click.echo(subheader("Configuration"))
    if DEFAULT_CONFIG_PATH.exists():
        print_success(f"Config file exists: {DEFAULT_CONFIG_PATH}")
    else:
        print_warning(f"Config file not found: {DEFAULT_CONFIG_PATH}")
        if fix:
            try:
                Config.ensure_exists()
                print_success("Created default configuration")
                fixes_applied += 1
            except Exception as e:
                print_error(f"Failed to create config: {e}")
        issues_found += 1

    # Check Ollama
    click.echo()
    click.echo(subheader("Ollama Service"))
    try:
        provider = OllamaProvider()
        # Try a simple request
        import asyncio

        async def test_ollama():
            try:
                from oclawma.providers.base import CompletionRequest, Message

                await provider.complete(
                    CompletionRequest(messages=[Message(role="user", content="hi")], model="")
                )
                return True
            except Exception:
                return False

        if asyncio.run(test_ollama()):
            print_success("Ollama is running and accessible")
        else:
            print_warning("Ollama is running but returned an error")
            issues_found += 1
    except Exception as e:
        print_warning(f"Ollama is not accessible: {e}")
        if fix:
            print_info("Try starting Ollama: ollama serve")
        issues_found += 1

    # Check API keys
    click.echo()
    click.echo(subheader("API Keys"))
    kimi_key = os.environ.get("KIMI_API_KEY")
    if kimi_key:
        masked = kimi_key[:4] + "..." + kimi_key[-4:] if len(kimi_key) > 12 else "***"
        print_success(f"KIMI_API_KEY is set ({masked})")
    else:
        print_info("KIMI_API_KEY not set (only needed for cloud provider)")

    # Check skills directory
    click.echo()
    click.echo(subheader("Skills"))
    skills_dir = Path.home() / ".oclawma" / "skills"
    if skills_dir.exists():
        skill_count = len(list(skills_dir.iterdir()))
        print_success(f"Skills directory exists ({skill_count} items)")
    else:
        print_info("Skills directory not created yet")
        if fix:
            skills_dir.mkdir(parents=True, exist_ok=True)
            print_success("Created skills directory")
            fixes_applied += 1

    # Summary
    click.echo()
    click.echo(header("DIAGNOSIS SUMMARY", width=58))
    if issues_found == 0:
        print_success("All checks passed! System is healthy.")
    else:
        print_warning(f"Found {issues_found} issue(s)")
        if fix and fixes_applied > 0:
            print_success(f"Applied {fixes_applied} automatic fix(es)")
        print_info("Run without --fix to see details, or with --fix to auto-fix")


def main() -> None:
    """Main entry point for the CLI."""
    cli()


if __name__ == "__main__":
    main()
