"""Configuration CLI commands for Oclawma."""

from __future__ import annotations

import os
from pathlib import Path

import click
import yaml

from oclawma.cli_ui import (
    ErrorHelper,
    accent,
    bullet,
    header,
    highlight,
    key_value,
    muted,
    print_error,
    print_info,
    print_success,
    print_warning,
    progress_spinner,
    subheader,
    success,
    warning,
)
from oclawma.config import (
    DEFAULT_CONFIG_PATH,
    Config,
    ModelProfile,
)


@click.group(name="config")
def config_cli() -> None:
    """Manage Oclawma configuration."""
    pass


@config_cli.command(name="show")
@click.option(
    "--path",
    type=click.Path(exists=False),
    help="Path to config file (default: ~/.oclawma/config.yaml)",
)
@click.option("--active", is_flag=True, help="Show only active profile")
def show_config(path: str | None, active: bool) -> None:
    """Display current configuration."""
    config_path = Path(path) if path else DEFAULT_CONFIG_PATH

    with progress_spinner("Loading configuration..."):
        try:
            config = Config.load(config_path)
        except Exception as e:
            print_error(f"Error loading config: {e}")
            raise click.ClickException(str(e)) from e

    click.echo(header("CONFIGURATION", width=58))
    click.echo()

    # Show config file location
    exists = config_path.exists()
    click.echo(subheader("FILE"))
    click.echo(key_value("Path", str(config_path)))
    click.echo(
        key_value("Status", success("Exists") if exists else warning("Not found (using defaults)"))
    )
    click.echo()

    if active:
        # Show only active profile
        click.echo(subheader("ACTIVE PROFILE"))
        profile = config.get_active_profile()
        _display_profile(profile)
    else:
        # Show all profiles
        click.echo(subheader("PROFILES"))
        for name, profile in config.profiles.items():
            is_active = name == config.active_profile
            prefix = accent("▶ ") if is_active else "  "
            suffix = accent(" (active)") if is_active else ""
            click.echo(f"{prefix}{highlight(name)}{suffix}")
            _display_profile(profile, indent=1)
            click.echo()

        # Show context budget
        click.echo(subheader("CONTEXT BUDGET"))
        click.echo(key_value("Total budget", f"{config.budget.total_budget:,} tokens"))
        warning_pct = _pct(config.budget.warning_threshold, config.budget.total_budget)
        critical_pct = _pct(config.budget.critical_threshold, config.budget.total_budget)
        click.echo(
            key_value("Warning at", f"{config.budget.warning_threshold:,} tokens ({warning_pct}%)")
        )
        click.echo(
            key_value(
                "Critical at", f"{config.budget.critical_threshold:,} tokens ({critical_pct}%)"
            )
        )
        click.echo(
            key_value("Strict mode", success("Yes") if config.budget.strict_mode else warning("No"))
        )
        click.echo()

        # Show fallback settings
        click.echo(subheader("FALLBACK SETTINGS"))
        click.echo(key_value("Enabled", success("Yes") if config.fallback.enabled else muted("No")))
        click.echo(key_value("Primary", config.fallback.primary_profile))
        click.echo(key_value("Fallback", config.fallback.fallback_profile))
        click.echo(
            key_value(
                "On overflow",
                success("Yes") if config.fallback.trigger_on_context_overflow else muted("No"),
            )
        )
        click.echo(
            key_value(
                "On connection error",
                success("Yes") if config.fallback.trigger_on_connection_error else muted("No"),
            )
        )
        click.echo()

        # Show global settings
        click.echo(subheader("GLOBAL SETTINGS"))
        click.echo(key_value("Default timeout", f"{config.default_timeout}s"))
        click.echo(key_value("Max retries", str(config.max_retries)))


def _display_profile(profile: ModelProfile, indent: int = 0) -> None:
    """Display a profile's details."""
    prefix = "  " * indent
    click.echo(f"{prefix}{key_value('Provider', profile.provider)}")
    click.echo(f"{prefix}{key_value('Model', profile.model)}")
    if profile.base_url:
        click.echo(f"{prefix}{key_value('Base URL', profile.base_url)}")

    api_key_status = success("Set") if profile.get_api_key() else warning("Not set")
    if profile.api_key_env:
        click.echo(
            f"{prefix}{key_value('API Key', f'{api_key_status} (from {profile.api_key_env})')}"
        )
    elif profile.api_key:
        click.echo(f"{prefix}{key_value('API Key', success('Set (in config)'))}")
    else:
        click.echo(f"{prefix}{key_value('API Key', warning('Not configured'))}")

    click.echo(f"{prefix}{key_value('Temperature', str(profile.temperature))}")
    click.echo(f"{prefix}{key_value('Top P', str(profile.top_p))}")
    if profile.max_tokens:
        click.echo(f"{prefix}{key_value('Max tokens', str(profile.max_tokens))}")
    click.echo(
        f"{prefix}{key_value('Enabled', success('Yes') if profile.enabled else muted('No'))}"
    )


def _pct(part: int, whole: int) -> int:
    """Calculate percentage."""
    return int((part / whole) * 100)


@config_cli.command(name="init")
@click.option(
    "--path",
    type=click.Path(exists=False),
    help="Path to create config file (default: ~/.oclawma/config.yaml)",
)
@click.option("--force", is_flag=True, help="Overwrite existing config")
def init_config(path: str | None, force: bool) -> None:
    """Initialize a new configuration file with defaults."""
    config_path = Path(path) if path else DEFAULT_CONFIG_PATH

    if config_path.exists() and not force:
        print_warning(f"Config already exists at {config_path}")
        print_info("Use --force to overwrite")
        return

    with progress_spinner("Creating configuration..."):
        try:
            config = Config()
            config.save(config_path)
        except Exception as e:
            print_error(f"Error creating config: {e}")
            raise click.ClickException(str(e)) from e

    print_success(f"Created config at {config_path}")
    click.echo()
    click.echo(subheader("DEFAULT PROFILES"))
    click.echo(f"  {bullet('local', level=0)} - Ollama provider (localhost:11434)")
    click.echo(f"  {bullet('cloud', level=0)} - Kimi cloud provider")
    click.echo(f"  {bullet('fallback', level=0)} - Kimi for overflow fallback")
    click.echo()
    print_info("Edit the file directly or use 'oclawma config set' to modify.")


@config_cli.command(name="set")
@click.argument("key")
@click.argument("value")
@click.option(
    "--profile",
    help="Profile to modify (for profile-specific settings)",
)
@click.option(
    "--path",
    type=click.Path(exists=False),
    help="Path to config file",
)
def set_config(key: str, value: str, profile: str | None, path: str | None) -> None:
    """Set a configuration value.

    Examples:
        oclawma config set active_profile cloud
        oclawma config set budget.total_budget 16384
        oclawma config set model llama3.2:latest --profile local
        oclawma config set api_key_env OPENAI_API_KEY --profile cloud
    """
    config_path = Path(path) if path else DEFAULT_CONFIG_PATH

    with progress_spinner("Updating configuration..."):
        try:
            config = Config.ensure_exists(config_path)

            # Handle profile-specific settings
            if profile:
                if profile not in config.profiles:
                    available = list(config.profiles.keys())
                    print_error(ErrorHelper.profile_not_found(profile, available))
                    raise click.Abort()

                prof = config.profiles[profile]

                # Map common keys to profile attributes
                key_map = {
                    "model": "model",
                    "provider": "provider",
                    "base_url": "base_url",
                    "api_key": "api_key",
                    "api_key_env": "api_key_env",
                    "temperature": "temperature",
                    "top_p": "top_p",
                    "max_tokens": "max_tokens",
                    "enabled": "enabled",
                }

                if key not in key_map:
                    print_error(f"Unknown profile key: {key}")
                    print_info(f"Valid keys: {', '.join(key_map.keys())}")
                    raise click.ClickException(f"Unknown profile key: {key}")

                attr = key_map[key]

                # Convert value types
                if key in ["temperature", "top_p"]:
                    value = float(value)
                elif key in ["max_tokens"]:
                    value = int(value) if value.lower() != "null" else None
                elif key == "enabled":
                    value = value.lower() in ["true", "yes", "1", "on"]
                elif key == "api_key" and value == "-":
                    # Read from stdin for sensitive data
                    value = click.prompt("API Key", hide_input=True)

                setattr(prof, attr, value)
                config.save(config_path)
                print_success(f"Set {key}={value} for profile '{profile}'")

            # Handle global settings
            elif key == "active_profile":
                if value not in config.profiles:
                    available = list(config.profiles.keys())
                    print_error(ErrorHelper.profile_not_found(value, available))
                    raise click.Abort()
                config.active_profile = value
                config.save(config_path)
                print_success(f"Set active profile to '{value}'")

            elif key.startswith("budget."):
                budget_key = key.split(".", 1)[1]
                if budget_key == "total_budget":
                    config.budget.total_budget = int(value)
                elif budget_key == "warning_threshold":
                    config.budget.warning_threshold = int(value)
                elif budget_key == "critical_threshold":
                    config.budget.critical_threshold = int(value)
                elif budget_key == "strict_mode":
                    config.budget.strict_mode = value.lower() in ["true", "yes", "1", "on"]
                else:
                    print_error(f"Unknown budget key: {budget_key}")
                    raise click.ClickException(f"Unknown budget key: {budget_key}")
                config.save(config_path)
                print_success(f"Set {key}={value}")

            elif key.startswith("fallback."):
                fallback_key = key.split(".", 1)[1]
                if fallback_key == "enabled":
                    config.fallback.enabled = value.lower() in ["true", "yes", "1", "on"]
                elif fallback_key == "primary_profile":
                    config.fallback.primary_profile = value
                elif fallback_key == "fallback_profile":
                    config.fallback.fallback_profile = value
                elif fallback_key == "trigger_on_context_overflow":
                    config.fallback.trigger_on_context_overflow = value.lower() in [
                        "true",
                        "yes",
                        "1",
                        "on",
                    ]
                elif fallback_key == "trigger_on_connection_error":
                    config.fallback.trigger_on_connection_error = value.lower() in [
                        "true",
                        "yes",
                        "1",
                        "on",
                    ]
                else:
                    print_error(f"Unknown fallback key: {fallback_key}")
                    raise click.ClickException(f"Unknown fallback key: {fallback_key}")
                config.save(config_path)
                print_success(f"Set {key}={value}")

            else:
                print_error(f"Unknown configuration key: {key}")
                print_info(
                    "Use: active_profile, budget.*, fallback.*, or --profile <name> for profile settings"
                )
                raise click.ClickException(f"Unknown key: {key}")

        except click.ClickException:
            raise
        except ValueError as e:
            print_error(f"Invalid value: {e}")
            raise click.ClickException(str(e)) from e
        except Exception as e:
            print_error(f"Error: {e}")
            raise click.ClickException(str(e)) from e


@config_cli.command(name="profile")
@click.argument("action", type=click.Choice(["add", "remove", "use", "show"]))
@click.argument("name", required=False)
@click.option("--provider", type=click.Choice(["ollama", "kimi", "openai", "anthropic"]))
@click.option("--model", help="Model name")
@click.option("--base-url", help="Base URL for the provider")
@click.option("--api-key-env", help="Environment variable name for API key")
@click.option(
    "--path",
    type=click.Path(exists=False),
    help="Path to config file",
)
def profile_command(
    action: str,
    name: str | None,
    provider: str | None,
    model: str | None,
    base_url: str | None,
    api_key_env: str | None,
    path: str | None,
) -> None:
    """Manage model profiles.

    Examples:
        oclawma config profile add mycloud --provider kimi --model k2.5
        oclawma config profile remove mycloud
        oclawma config profile use cloud
        oclawma config profile show local
    """
    config_path = Path(path) if path else DEFAULT_CONFIG_PATH

    try:
        with progress_spinner(f"{action.capitalize()} profile..."):
            config = Config.ensure_exists(config_path)

            if action == "add":
                if not name:
                    print_error("Profile name is required for 'add'")
                    raise click.ClickException("Profile name is required")
                if not provider:
                    print_error("--provider is required for 'add'")
                    raise click.ClickException("--provider is required")

                if name in config.profiles:
                    print_warning(
                        f"Profile '{name}' already exists. Use 'remove' first or --force."
                    )
                    raise click.ClickException(f"Profile '{name}' already exists")

                profile = ModelProfile(
                    name=name,
                    provider=provider,  # type: ignore
                    model=model or ("kimi-k2-5" if provider == "kimi" else "qwen2.5:3b"),
                    base_url=base_url,
                    api_key_env=api_key_env,
                )
                config.add_profile(profile)
                config.save(config_path)
                print_success(f"Added profile '{name}'")

            elif action == "remove":
                if not name:
                    print_error("Profile name is required for 'remove'")
                    raise click.ClickException("Profile name is required")

                if name not in config.profiles:
                    available = list(config.profiles.keys())
                    print_error(ErrorHelper.profile_not_found(name, available))
                    raise click.ClickException(f"Profile '{name}' not found")

                if len(config.profiles) <= 1:
                    print_error("Cannot remove the last profile")
                    raise click.ClickException("Cannot remove the last profile")

                if config.active_profile == name:
                    print_warning(
                        f"'{name}' is the active profile. Switching to another profile first."
                    )
                    # Switch to another profile
                    for p in config.profiles:
                        if p != name:
                            config.active_profile = p
                            break

                config.remove_profile(name)
                config.save(config_path)
                print_success(f"Removed profile '{name}'")

            elif action == "use":
                if not name:
                    print_error("Profile name is required for 'use'")
                    raise click.ClickException("Profile name is required")

                if name not in config.profiles:
                    available = list(config.profiles.keys())
                    print_error(ErrorHelper.profile_not_found(name, available))
                    raise click.ClickException(f"Profile '{name}' not found")

                config.set_active_profile(name)
                config.save(config_path)
                print_success(f"Now using profile '{name}'")

            elif action == "show":
                if name:
                    if name not in config.profiles:
                        available = list(config.profiles.keys())
                        print_error(ErrorHelper.profile_not_found(name, available))
                        raise click.ClickException(f"Profile '{name}' not found")
                    click.echo(header(f"PROFILE: {name}", width=58))
                    _display_profile(config.profiles[name])
                else:
                    click.echo(header("AVAILABLE PROFILES", width=58))
                    for pname in config.list_profiles():
                        marker = accent("▶ ") if pname == config.active_profile else "  "
                        click.echo(f"{marker}{pname}")

    except click.ClickException:
        raise
    except Exception as e:
        print_error(f"Error: {e}")
        raise click.ClickException(str(e)) from e


@click.option(
    "--path",
    type=click.Path(exists=False),
    help="Path to config file",
)
@click.option("--editor", help="Editor to use (default: $EDITOR or nano)")
def edit_config(path: str | None, editor: str | None) -> None:
    """Open configuration file in editor."""
    config_path = Path(path) if path else DEFAULT_CONFIG_PATH

    # Ensure config exists
    Config.ensure_exists(config_path)

    # Determine editor
    ed = editor or os.environ.get("EDITOR", "nano")

    print_info(f"Opening {config_path} in {ed}...")

    import subprocess

    try:
        subprocess.run([ed, str(config_path)], check=True)
        print_success("Config updated")
    except subprocess.CalledProcessError as e:
        print_error(f"Editor exited with error: {e}")
        raise click.ClickException(str(e)) from e
    except FileNotFoundError:
        print_error(f"Editor '{ed}' not found")
        print_info("Install an editor or set $EDITOR environment variable")
        raise click.ClickException(f"Editor '{ed}' not found") from None


@config_cli.command(name="path")
def config_path_cmd() -> None:
    """Show the configuration file path."""
    click.echo(highlight(str(DEFAULT_CONFIG_PATH)))


@config_cli.command(name="validate")
@click.option(
    "--path",
    type=click.Path(exists=False),
    help="Path to config file",
)
def validate_config(path: str | None) -> None:
    """Validate the configuration file."""
    config_path = Path(path) if path else DEFAULT_CONFIG_PATH

    with progress_spinner("Validating configuration..."):
        issues = []
        warnings_list = []

        try:
            if not config_path.exists():
                issues.append(f"Config file does not exist: {config_path}")
            else:
                config = Config.load(config_path)

                # Validate profiles
                if not config.profiles:
                    issues.append("No profiles configured")

                for name, profile in config.profiles.items():
                    if not profile.model:
                        issues.append(f"Profile '{name}' has no model specified")

                    if profile.provider == "kimi" and not profile.get_api_key():
                        warnings_list.append(f"Profile '{name}' (kimi) has no API key set")

                    if profile.temperature < 0 or profile.temperature > 2:
                        warnings_list.append(
                            f"Profile '{name}' has unusual temperature: {profile.temperature}"
                        )

                # Validate active profile
                if config.active_profile not in config.profiles:
                    issues.append(f"Active profile '{config.active_profile}' does not exist")

                # Validate budget
                if config.budget.warning_threshold >= config.budget.critical_threshold:
                    warnings_list.append("Warning threshold should be less than critical threshold")

                # Validate fallback
                if config.fallback.enabled:
                    if config.fallback.primary_profile not in config.profiles:
                        issues.append(
                            f"Fallback primary profile '{config.fallback.primary_profile}' does not exist"
                        )
                    if config.fallback.fallback_profile not in config.profiles:
                        issues.append(
                            f"Fallback profile '{config.fallback.fallback_profile}' does not exist"
                        )

        except yaml.YAMLError as e:
            issues.append(f"Invalid YAML syntax: {e}")
        except Exception as e:
            issues.append(f"Error loading config: {e}")

    # Report results
    click.echo(header("VALIDATION RESULTS", width=58))

    if issues:
        click.echo()
        click.echo(subheader("ERRORS"))
        for issue in issues:
            print_error(issue)

    if warnings_list:
        click.echo()
        click.echo(subheader("WARNINGS"))
        for w in warnings_list:
            print_warning(w)

    if not issues and not warnings_list:
        print_success("Configuration is valid!")
    elif not issues:
        print_info("\nConfiguration is valid but has warnings")
    else:
        raise click.ClickException(f"Found {len(issues)} error(s)")
