"""cortex/cli/admin_cmds.py — providers, extraction-instructions."""
from __future__ import annotations

import json
import os
from typing import Optional

import click

import cortex.cli.app as _app

main = _app.main


# ---------------------------------------------------------------------------
# Provider → env var mappings
# ---------------------------------------------------------------------------

_PROVIDER_ENV_KEYS: dict[str, str] = {
    "google": "GOOGLE_API_KEY",
    "anthropic": "ANTHROPIC_API_KEY",
    "openai": "OPENAI_API_KEY",
    "venice": "VENICE_API_KEY",
    "ollama": "",  # Ollama doesn't need an API key
}

_EMBEDDING_ENV_KEYS: dict[str, str] = {
    "voyage": "VOYAGE_API_KEY",
    "openai": "OPENAI_API_KEY",
    "google": "GOOGLE_API_KEY",
}

_PROVIDER_DEFAULT_MODELS: dict[str, str] = {
    "google": "gemini-2.5-flash",
    "anthropic": "claude-haiku-4-5",
    "openai": "gpt-4.1-mini",
    "venice": "grok-41-fast",
    "ollama": "llama3",
}


def _resolve_providers(config_path: Optional[str]) -> dict:
    """Load config and resolve provider status into a dict."""
    from cortex.config import CortexConfig

    cfg = CortexConfig.from_toml(config_path)

    result: dict = {
        "config_path": str(config_path) if config_path else "(defaults only)",
        "provider_mode": cfg.provider_mode,
    }

    # --- LLM cascade (legacy provider_priority) ---
    cascade = []
    for name in cfg.llm_provider_priority:
        env_var = _PROVIDER_ENV_KEYS.get(name, "")
        key_set = (not env_var) or bool(os.environ.get(env_var))
        default_model = _PROVIDER_DEFAULT_MODELS.get(name, "")
        supports_embed = name not in ("venice", "ollama")
        cascade.append({
            "name": name,
            "model": default_model,
            "env_var": env_var,
            "key_set": key_set,
            "supports_embed": supports_embed,
        })
    result["llm_cascade"] = cascade

    # --- Provider architecture (v1.2.1) ---
    result["provider_llm"] = {
        "provider": cfg.provider_llm_provider or "(auto)",
        "model": cfg.provider_llm_model,
        "api_key_set": bool(cfg.provider_llm_api_key),
    }

    # --- Embeddings ---
    emb_model = cfg.provider_embedding_model or cfg.embedding_model
    emb_dim = cfg.provider_embedding_dim or cfg.embedding_dim

    if "voyage" in emb_model.lower():
        emb_provider = "voyage"
    elif "gemini" in emb_model.lower():
        emb_provider = "google"
    else:
        emb_provider = cfg.provider_embedding_provider or "openai"

    emb_env_var = _EMBEDDING_ENV_KEYS.get(emb_provider, "")
    emb_key_set = bool(cfg.provider_embedding_api_key) or bool(os.environ.get(emb_env_var))
    result["embedding"] = {
        "model": emb_model,
        "dim": emb_dim,
        "provider": emb_provider,
        "env_var": emb_env_var,
        "key_set": emb_key_set,
    }

    # Google embedding (secondary)
    google_env = "GOOGLE_API_KEY"
    result["google_embedding"] = {
        "env_var": google_env,
        "key_set": bool(os.environ.get(google_env)) or bool(os.environ.get("GEMINI_API_KEY")),
    }

    # --- Extraction & Reconciliation models ---
    result["extraction_model"] = cfg.extraction_model
    result["reconciliation_model"] = cfg.reconciliation_model

    return result


@main.command("providers")
@click.option("--json", "as_json", is_flag=True, default=False,
              help="Output as machine-readable JSON.")
@click.pass_context
def cmd_providers(ctx: click.Context, as_json: bool) -> None:
    """Show resolved provider configuration and API key status."""
    info = _resolve_providers(ctx.obj["config"])

    if as_json:
        click.echo(json.dumps(info, indent=2))
        return

    # --- Rich panel output ---
    if _app._RICH:
        from rich.panel import Panel
        from rich.text import Text

        lines = Text()
        lines.append(f"Mode: {info['provider_mode']}", style="bold")
        lines.append(f" (from {info['config_path']})\n")

        if info["provider_llm"]["provider"] != "(auto)" or info["provider_llm"]["api_key_set"]:
            lines.append("\n")
            lines.append("Provider LLM: ", style="bold")
            lines.append(f"{info['provider_llm']['provider']} — {info['provider_llm']['model']}")
            key_icon = "✅" if info["provider_llm"]["api_key_set"] else "❌"
            lines.append(f" ({key_icon} api_key {'set' if info['provider_llm']['api_key_set'] else 'missing'})\n")

        lines.append("\n")
        lines.append("LLM Cascade:\n", style="bold")
        for i, p in enumerate(info["llm_cascade"], 1):
            icon = "✅" if p["key_set"] else "❌"
            env_note = f" ({p['env_var']} missing)" if not p["key_set"] and p["env_var"] else ""
            key_note = " (key set)" if p["key_set"] and p["env_var"] else ""
            no_key_note = " (no key needed)" if not p["env_var"] else ""
            lines.append(f"  {i}. {icon} {p['name']} — {p['model']}{key_note}{env_note}{no_key_note}\n")

        lines.append("\n")
        emb = info["embedding"]
        lines.append("Embeddings: ", style="bold")
        lines.append(f"{emb['model']} (dim={emb['dim']})\n")
        emb_icon = "✅" if emb["key_set"] else "❌"
        lines.append(f"  {emb_icon} {emb['env_var']} {'set' if emb['key_set'] else 'missing'}\n")

        google = info["google_embedding"]
        lines.append("\n")
        lines.append("Google Embeddings:\n", style="bold")
        g_icon = "✅" if google["key_set"] else "❌"
        lines.append(f"  {g_icon} {google['env_var']} {'set' if google['key_set'] else 'missing'}\n")

        lines.append("\n")
        lines.append(f"Extraction: {info['extraction_model']}\n")
        lines.append(f"Reconciliation: {info['reconciliation_model']}\n")
        lines.append("\n")
        lines.append(f"Config: {info['config_path']}\n")

        panel = Panel(lines, title="Provider Configuration", border_style="cyan")
        _app._console.print(panel)  # type: ignore[union-attr]
    else:
        click.echo("\n--- Provider Configuration ---")
        click.echo(f"Mode: {info['provider_mode']} (from {info['config_path']})")

        if info["provider_llm"]["provider"] != "(auto)" or info["provider_llm"]["api_key_set"]:
            click.echo(f"\nProvider LLM: {info['provider_llm']['provider']} — {info['provider_llm']['model']}")
            key_s = "set" if info["provider_llm"]["api_key_set"] else "missing"
            click.echo(f"  api_key: {key_s}")

        click.echo("\nLLM Cascade:")
        for i, p in enumerate(info["llm_cascade"], 1):
            icon = "✓" if p["key_set"] else "✗"
            env_note = f" ({p['env_var']} missing)" if not p["key_set"] and p["env_var"] else ""
            key_note = " (key set)" if p["key_set"] and p["env_var"] else ""
            no_key_note = " (no key needed)" if not p["env_var"] else ""
            click.echo(f"  {i}. {icon} {p['name']} — {p['model']}{key_note}{env_note}{no_key_note}")

        emb = info["embedding"]
        click.echo(f"\nEmbeddings: {emb['model']} (dim={emb['dim']})")
        click.echo(f"  {emb['env_var']}: {'set' if emb['key_set'] else 'missing'}")

        google = info["google_embedding"]
        click.echo("\nGoogle Embeddings:")
        click.echo(f"  {google['env_var']}: {'set' if google['key_set'] else 'missing'}")

        click.echo(f"\nExtraction: {info['extraction_model']}")
        click.echo(f"Reconciliation: {info['reconciliation_model']}")
        click.echo(f"\nConfig: {info['config_path']}")
        click.echo()


# ---------------------------------------------------------------------------
# extraction-instructions subcommand group (#177)
# ---------------------------------------------------------------------------

@main.group("extraction-instructions")
@click.pass_context
def extraction_instructions(ctx: click.Context) -> None:
    """Manage custom extraction instructions for the Cortex memory pipeline."""


@extraction_instructions.command("get")
@click.pass_context
def extraction_instructions_get(ctx: click.Context) -> None:
    """Print the current custom extraction instructions."""
    plugin, _ = _app._make_plugin(ctx)

    instructions = getattr(plugin.config, "extraction_custom_instructions", "")
    if instructions:
        click.echo(instructions)
    else:
        click.echo("(no custom extraction instructions set)")


@extraction_instructions.command("set")
@click.argument("instructions")
@click.pass_context
def extraction_instructions_set(ctx: click.Context, instructions: str) -> None:
    """Set custom extraction instructions.

    INSTRUCTIONS is the operator instruction string to append to the
    extraction system prompt.  Pass an empty string to clear.

    Example::

        evaos extraction-instructions set "Always extract cooking preferences."
        evaos extraction-instructions set ""  # clears instructions
    """
    from cortex.extraction.extractor import ExtractionPipeline
    from cortex.extraction.prompts import EXTRACTION_SYSTEM_PROMPT

    plugin, _ = _app._make_plugin(ctx)

    instructions = instructions.strip()
    plugin.config.extraction_custom_instructions = instructions

    # Also update the live extractor so it takes effect immediately
    extractor = getattr(plugin, "extractor", None)
    if extractor is not None and isinstance(extractor, ExtractionPipeline):
        extractor.custom_instructions = instructions
        if instructions:
            extractor._system_prompt = (
                EXTRACTION_SYSTEM_PROMPT
                + "\n\n## Operator Instructions\n"
                + instructions
            )
        else:
            extractor._system_prompt = EXTRACTION_SYSTEM_PROMPT

    if instructions:
        _app._ok(f"Extraction instructions updated:\n{instructions}")
    else:
        _app._ok("Extraction instructions cleared.")
