"""Config command implementation: interactive configuration menu."""
import json
import os
import re
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple

import questionary
from rich.table import Table

from armorer.cli import common as cli_common
from armorer.cli.model_catalog import CUSTOM_MODEL_CHOICE, DEFAULT_MODELS, get_latest_models
from armorer.config import ConfigManager


def config_impl(print_logo: Callable[[], None], armorer_config_dir: Callable[[], Path]) -> None:
    """Manage Armorer configuration and model settings."""
    print_logo()
    config_manager = ConfigManager()

    choices = [
        "Change Model",
        "Update API Keys",
        "Configure Guard (LLM Guard)",
        "Configure Daily Reports",
        "Configure Langfuse",
        "View Current Config",
        "Back",
    ]

    while True:
        choice = questionary.select("Settings:", choices=choices).ask()
        if not choice or choice == "Back":
            break

        if choice == "Change Model":
            provider = questionary.select(
                "Select Provider:",
                choices=["openai", "anthropic", "gemini", "bedrock", "openrouter", "ollama"],
            ).ask()
            if not provider:
                continue
            config_manager.set_key("MODEL_PROVIDER", provider)

            default_model = DEFAULT_MODELS.get(provider, "gpt-4o")
            model_choices = get_latest_models(provider, config_manager)
            model_selection = questionary.select(
                "Select Model ID:",
                choices=model_choices + [CUSTOM_MODEL_CHOICE],
                default=default_model if default_model in model_choices else None,
            ).ask()
            if not model_selection:
                continue
            if model_selection == CUSTOM_MODEL_CHOICE:
                model = questionary.text(
                    f"Enter custom Model ID (default: {default_model}):",
                    default=default_model,
                ).ask()
                model = model.strip() if model else default_model
            else:
                model = str(model_selection).strip()
            config_manager.set_key("Armorer_MODEL", model)

            if provider == "ollama":
                base_url = questionary.text(
                    "Enter Ollama Base URL (default: http://localhost:11434/v1/)"
                ).ask()
                if not base_url:
                    base_url = "http://localhost:11434/v1/"

                # Check connectivity
                with cli_common.console.status(f"[bold cyan]Testing connectivity to {base_url}..."):
                    try:
                        import httpx

                        # Try to list models (OpenAI compatible endpoint)
                        resp = httpx.get(f"{base_url.rstrip('/')}/models", timeout=5.0)
                        if resp.status_code == 200:
                            cli_common.console.print(f"[green]✓ Ollama is reachable![/green]")
                        else:
                            cli_common.console.print(
                                f"[yellow]⚠ Ollama endpoint returned {resp.status_code}. Proceeding anyway.[/yellow]"
                            )
                    except Exception:
                        cli_common.console.print(
                            f"[yellow]⚠ Could not reach Ollama at {base_url}. Make sure it is running (`ollama serve`). Proceeding anyway.[/yellow]"
                        )

                config_manager.set_key("OLLAMA_BASE_URL", base_url)

            cli_common.console.print(
                f"[green]✓ Provider set to [bold]{provider}[/bold], model: [bold]{model}[/bold][/green]"
            )

        elif choice == "Update API Keys":
            provider = questionary.select(
                "Which provider?",
                choices=["openai", "anthropic", "gemini", "bedrock", "openrouter", "ollama"],
            ).ask()
            if provider:
                key_name = f"{provider.upper()}_API_KEY"
                key_val = questionary.password(f"Enter {key_name}:").ask()
                if key_val:
                    config_manager.save_to_vault(
                        key_name, key_val, description=f"{provider.capitalize()} API Key"
                    )
                    cli_common.console.print(f"[green]✓ {key_name} updated in vault.[/green]")

        elif choice == "Configure Langfuse":
            current_lf = (
                config_manager.get_key("LANGFUSE_ENABLED") or "false"
            ).lower() == "true"
            toggle_label = "Disable (currently on)" if current_lf else "Enable (currently off)"
            lf_action = questionary.select(
                "Langfuse Observability:",
                choices=[toggle_label, "Set API Keys", "Set Base URL", "Back"],
            ).ask()
            if not lf_action or lf_action == "Back":
                continue

            if lf_action == toggle_label:
                if current_lf:
                    # Disabling — no validation needed
                    config_manager.set_key("LANGFUSE_ENABLED", "false")
                    cli_common.console.print("[green]✗ Disabled[/green] Langfuse tracing")
                else:
                    # Enabling — ensure credentials are present
                    pub = config_manager.find_vault_key("LANGFUSE_PUBLIC_KEY")
                    sec = config_manager.find_vault_key("LANGFUSE_SECRET_KEY")
                    base = config_manager.get_key("LANGFUSE_BASE_URL")

                    if not pub:
                        pub = questionary.password("LANGFUSE_PUBLIC_KEY is required:").ask()
                        if pub:
                            config_manager.save_to_vault(
                                "LANGFUSE_PUBLIC_KEY", pub, description="Langfuse Public Key"
                            )
                    if not sec:
                        sec = questionary.password("LANGFUSE_SECRET_KEY is required:").ask()
                        if sec:
                            config_manager.save_to_vault(
                                "LANGFUSE_SECRET_KEY", sec, description="Langfuse Secret Key"
                            )
                    if not base:
                        region = questionary.select(
                            "Langfuse Region:",
                            choices=[
                                "EU (cloud.langfuse.com)",
                                "US (us.cloud.langfuse.com)",
                                "Custom / Self-hosted",
                            ],
                        ).ask()
                        if region and "EU" in region:
                            base = "https://cloud.langfuse.com"
                        elif region and "US" in region:
                            base = "https://us.cloud.langfuse.com"
                        elif region:
                            base = questionary.text("Enter Langfuse base URL:").ask()
                        if base:
                            config_manager.set_key("LANGFUSE_BASE_URL", base)

                    if pub and sec:
                        config_manager.set_key("LANGFUSE_ENABLED", "true")
                        cli_common.console.print("[green]✓ Enabled[/green] Langfuse tracing")
                    else:
                        cli_common.console.print(
                            "[red]✗ Cannot enable — missing API keys.[/red]"
                        )

            elif lf_action == "Set API Keys":
                pub = questionary.password("Enter LANGFUSE_PUBLIC_KEY:").ask()
                if pub:
                    config_manager.save_to_vault(
                        "LANGFUSE_PUBLIC_KEY", pub, description="Langfuse Public Key"
                    )
                    cli_common.console.print("[green]✓ LANGFUSE_PUBLIC_KEY saved[/green]")
                sec = questionary.password("Enter LANGFUSE_SECRET_KEY:").ask()
                if sec:
                    config_manager.save_to_vault(
                        "LANGFUSE_SECRET_KEY", sec, description="Langfuse Secret Key"
                    )
                    cli_common.console.print("[green]✓ LANGFUSE_SECRET_KEY saved[/green]")

            elif lf_action == "Set Base URL":
                region = questionary.select(
                    "Langfuse Region:",
                    choices=[
                        "EU (cloud.langfuse.com)",
                        "US (us.cloud.langfuse.com)",
                        "Custom / Self-hosted",
                    ],
                ).ask()
                if region and "EU" in region:
                    config_manager.set_key("LANGFUSE_BASE_URL", "https://cloud.langfuse.com")
                elif region and "US" in region:
                    config_manager.set_key("LANGFUSE_BASE_URL", "https://us.cloud.langfuse.com")
                elif region:
                    url = questionary.text("Enter Langfuse base URL:").ask()
                    if url:
                        config_manager.set_key("LANGFUSE_BASE_URL", url)
                if region:
                    cli_common.console.print(
                        f"[green]✓ Langfuse base URL updated[/green]"
                    )

        elif choice == "Configure Guard (LLM Guard)":
            from armorer.security.conversation_guard import ConversationGuard

            guard = ConversationGuard(config_dir=armorer_config_dir())

            while True:
                settings = guard.get_guard_settings()
                status = guard.llm_guard_status()
                enabled = bool(settings.get("llm_guard_enabled", True))
                state_label = "on" if enabled else "off"
                available_label = (
                    "available" if status.get("available") else "not installed"
                )
                sub_choice = questionary.select(
                    f"Guard settings (LLM Guard is {state_label}, package {available_label}):",
                    choices=[
                        "Toggle LLM Guard",
                        "Configure Input Scanners",
                        "Configure Output Scanners",
                        "View Guard Status",
                        "Back",
                    ],
                ).ask()

                if not sub_choice or sub_choice == "Back":
                    break

                if sub_choice == "Toggle LLM Guard":
                    settings["llm_guard_enabled"] = not enabled
                    guard.save_guard_settings(settings)
                    cli_common.console.print(
                        f"[green]✓ LLM Guard {'enabled' if settings['llm_guard_enabled'] else 'disabled'} in guard settings.[/green]"
                    )
                    continue

                if sub_choice in {"Configure Input Scanners", "Configure Output Scanners"}:
                    scanner_type = "input" if "Input" in sub_choice else "output"
                    scanner_key = f"{scanner_type}_scanners"
                    signatures = guard.available_scanners(scanner_type)
                    scanner_cfg = settings.get(scanner_key, {})
                    scanner_cfg = (
                        scanner_cfg if isinstance(scanner_cfg, dict) else {}
                    )
                    unsupported = {"Anonymize", "Deanonymize"}
                    hidden = sorted([n for n in signatures.keys() if n in unsupported])
                    names = sorted([n for n in signatures.keys() if n not in unsupported])
                    if not names:
                        cli_common.console.print(
                            f"[yellow]No {scanner_type} scanners discovered.[/yellow]"
                        )
                        continue
                    if hidden:
                        cli_common.console.print(
                            f"[dim]Hidden unsupported scanners for this UI: {', '.join(hidden)}[/dim]"
                        )

                    current_enabled = {
                        n
                        for n, cfg in scanner_cfg.items()
                        if isinstance(cfg, dict) and bool(cfg.get("enabled"))
                    }

                    def _plain_sig(sig: str, max_len: int = 88) -> str:
                        text = " ".join(str(sig or "").split())
                        if len(text) <= max_len:
                            return text
                        return text[: max_len - 1].rstrip() + "…"

                    q_choices = [
                        questionary.Choice(
                            title=f"{name}  ({_plain_sig(signatures.get(name, '(unknown)'))})",
                            value=name,
                            checked=name in current_enabled,
                        )
                        for name in names
                    ]
                    selected = questionary.checkbox(
                        f"Select enabled {scanner_type} scanners:",
                        choices=q_choices,
                    ).ask()
                    if selected is None:
                        continue

                    selected_set = set(selected)
                    new_cfg: dict[str, Any] = {}
                    cancelled = False

                    scanner_presets: Dict[
                        str, List[Tuple[str, str, Dict[str, Any]]]
                    ] = {
                        "PromptInjection": [
                            (
                                "Recommended",
                                "Balanced detection with scanner defaults.",
                                {},
                            ),
                            (
                                "Strict",
                                "Catch more attacks (higher sensitivity).",
                                {"threshold": 0.85},
                            ),
                            (
                                "Conservative",
                                "Fewer false positives.",
                                {"threshold": 0.95},
                            ),
                        ],
                        "Secrets": [
                            (
                                "Recommended",
                                "Redact all detected secrets.",
                                {},
                            ),
                            (
                                "Mask only",
                                "Mask secret substrings while preserving shape.",
                                {"redact_mode": "mask"},
                            ),
                        ],
                        "TokenLimit": [
                            (
                                "Recommended",
                                "Limit to 8192 tokens per message.",
                                {"limit": 8192},
                            ),
                            ("Tight", "Limit to 4096 tokens.", {"limit": 4096}),
                            ("Large", "Limit to 16384 tokens.", {"limit": 16384}),
                        ],
                        "Toxicity": [
                            ("Recommended", "Balanced toxicity filtering.", {}),
                            ("Strict", "Stronger filtering.", {"threshold": 0.4}),
                            (
                                "Conservative",
                                "Filter only high toxicity.",
                                {"threshold": 0.8},
                            ),
                        ],
                        "Sensitive": [
                            (
                                "Recommended",
                                "Detect sensitive entities using defaults.",
                                {},
                            ),
                            (
                                "Strict",
                                "Flag more potential sensitive entities.",
                                {"threshold": 0.35},
                            ),
                            (
                                "Conservative",
                                "Flag only stronger sensitive matches.",
                                {"threshold": 0.7},
                            ),
                        ],
                        "Regex": [
                            ("Recommended", "No custom patterns yet.", {}),
                            (
                                "Example blocklist",
                                "Example patterns for token-like strings.",
                                {
                                    "patterns": [
                                        "(?i)api[_-]?key\\s*[:=]\\s*[A-Za-z0-9_-]{16,}"
                                    ],
                                    "is_blocked": True,
                                },
                            ),
                        ],
                    }

                    def _kwargs_choice(
                        scanner_name: str, existing_kwargs: Dict[str, Any]
                    ) -> Optional[Dict[str, Any]]:
                        presets = scanner_presets.get(scanner_name, [])
                        choices: List[questionary.Choice] = []
                        if existing_kwargs:
                            choices.append(
                                questionary.Choice(
                                    title=f"Keep current ({json.dumps(existing_kwargs, separators=(',', ':'))})",
                                    value=("keep", existing_kwargs),
                                )
                            )
                        for label, desc, payload in presets:
                            preview = json.dumps(payload, separators=(",", ":"))
                            choices.append(
                                questionary.Choice(
                                    title=f"{label}: {desc}  {preview}",
                                    value=("preset", payload),
                                )
                            )
                        choices.append(
                            questionary.Choice(
                                title="Custom JSON (advanced)",
                                value=("custom", None),
                            )
                        )
                        choice = questionary.select(
                            f"{scanner_name}: choose kwargs preset",
                            choices=choices,
                        ).ask()
                        if choice is None:
                            return None
                        mode, payload = choice
                        if mode in {"keep", "preset"}:
                            return dict(payload)
                        raw = questionary.text(
                            f"{scanner_name} kwargs JSON object.\n"
                            "Accepted value types: string, number, boolean, array, object.\n"
                            'Example: {"threshold":0.9} or {"limit":8192}',
                            default=json.dumps(
                                existing_kwargs, separators=(",", ":")
                            ),
                        ).ask()
                        if raw is None:
                            return None
                        text = raw.strip()
                        if not text:
                            return dict(existing_kwargs)
                        try:
                            parsed = json.loads(text)
                            if not isinstance(parsed, dict):
                                raise ValueError("must be a JSON object")
                            return parsed
                        except Exception as e:
                            cli_common.console.print(
                                f"[yellow]⚠ Invalid JSON for {scanner_name} kwargs ({e}); keeping previous kwargs.[/yellow]"
                            )
                            return dict(existing_kwargs)

                    for name in names:
                        prev = scanner_cfg.get(name, {})
                        prev = prev if isinstance(prev, dict) else {}
                        kwargs_obj = prev.get("kwargs", {})
                        kwargs_obj = (
                            kwargs_obj if isinstance(kwargs_obj, dict) else {}
                        )

                        if name in selected_set:
                            selected_kwargs = _kwargs_choice(name, kwargs_obj)
                            if selected_kwargs is None:
                                cancelled = True
                                break
                            new_cfg[name] = {
                                "enabled": True,
                                "kwargs": selected_kwargs,
                            }
                        else:
                            new_cfg[name] = {
                                "enabled": False,
                                "kwargs": kwargs_obj,
                            }

                    if cancelled:
                        cli_common.console.print(
                            "[yellow]Scanner update canceled; no changes were saved.[/yellow]"
                        )
                        continue

                    settings[scanner_key] = new_cfg
                    guard.save_guard_settings(settings)
                    cli_common.console.print(
                        f"[green]✓ Updated {scanner_type} scanner configuration.[/green]"
                    )
                    continue

                if sub_choice == "View Guard Status":
                    settings = guard.get_guard_settings()
                    status = guard.llm_guard_status()
                    table = Table(
                        title="Guard / LLM Guard Status", border_style="cyan"
                    )
                    table.add_column("Key", style="bold")
                    table.add_column("Value")
                    table.add_row(
                        "settings_file", str(status.get("settings_file", ""))
                    )
                    table.add_row(
                        "llm_guard_available", str(status.get("available"))
                    )
                    table.add_row(
                        "llm_guard_enabled", str(status.get("enabled"))
                    )
                    table.add_row(
                        "scanners_ready", str(status.get("scanners_ready"))
                    )
                    table.add_row(
                        "input_loaded",
                        ", ".join(status.get("input_loaded", [])) or "-",
                    )
                    table.add_row(
                        "output_loaded",
                        ", ".join(status.get("output_loaded", [])) or "-",
                    )
                    init_errors = status.get("init_errors", [])
                    table.add_row(
                        "init_errors",
                        "; ".join(init_errors) if init_errors else "-",
                    )
                    table.add_row(
                        "enabled_input_scanners",
                        ", ".join(
                            sorted(
                                k
                                for k, v in (
                                    settings.get("input_scanners", {}) or {}
                                ).items()
                                if isinstance(v, dict) and v.get("enabled")
                            )
                        )
                        or "-",
                    )
                    table.add_row(
                        "enabled_output_scanners",
                        ", ".join(
                            sorted(
                                k
                                for k, v in (
                                    settings.get("output_scanners", {}) or {}
                                ).items()
                                if isinstance(v, dict) and v.get("enabled")
                            )
                        )
                        or "-",
                    )
                    cli_common.console.print(table)

        elif choice == "Configure Daily Reports":
            enabled_raw = (
                config_manager.get_key("ARMORER_DAILY_REPORT_ENABLED") or "true"
            ).strip().lower()
            enabled = enabled_raw not in {"0", "false", "off", "no"}
            current_time = (
                config_manager.get_key("ARMORER_DAILY_REPORT_TIME") or "09:00"
            ).strip()
            current_window = (
                config_manager.get_key("ARMORER_DAILY_REPORT_WINDOW_HOURS") or "24"
            ).strip()

            action = questionary.select(
                f"Daily reports are currently {'ON' if enabled else 'OFF'}",
                choices=[
                    "Toggle On/Off",
                    "Set Send Time (HH:MM)",
                    "Set Window Hours",
                    "Back",
                ],
            ).ask()
            if not action or action == "Back":
                continue

            if action == "Toggle On/Off":
                config_manager.set_key(
                    "ARMORER_DAILY_REPORT_ENABLED",
                    "false" if enabled else "true",
                )
                cli_common.console.print(
                    f"[green]✓ Daily reports {'enabled' if not enabled else 'disabled'}.[/green]"
                )
                continue

            if action == "Set Send Time (HH:MM)":
                new_time = questionary.text(
                    "Enter daily report time (HH:MM, local timezone):",
                    default=current_time if current_time else "09:00",
                ).ask()
                if new_time and re.match(
                    r"^[0-2][0-9]:[0-5][0-9]$", new_time.strip()
                ):
                    config_manager.set_key(
                        "ARMORER_DAILY_REPORT_TIME", new_time.strip()
                    )
                    cli_common.console.print(
                        f"[green]✓ Daily report time set to {new_time.strip()}.[/green]"
                    )
                else:
                    cli_common.console.print(
                        "[yellow]⚠ Invalid time format. Use HH:MM.[/yellow]"
                    )
                continue

            if action == "Set Window Hours":
                new_window = questionary.text(
                    "Enter window hours (1-168):",
                    default=current_window if current_window else "24",
                ).ask()
                try:
                    val = int(str(new_window).strip())
                    if val < 1 or val > 168:
                        raise ValueError("out of range")
                    config_manager.set_key(
                        "ARMORER_DAILY_REPORT_WINDOW_HOURS", str(val)
                    )
                    cli_common.console.print(
                        f"[green]✓ Daily report window set to {val} hours.[/green]"
                    )
                except Exception:
                    cli_common.console.print(
                        "[yellow]⚠ Invalid number. Use an integer from 1 to 168.[/yellow]"
                    )
                continue

        elif choice == "View Current Config":
            table = Table(title="Current Configuration", border_style="cyan")
            table.add_column("Key", style="bold")
            table.add_column("Value")

            env = config_manager.load_env()
            table.add_row(
                "MODEL_PROVIDER",
                env.get("MODEL_PROVIDER", os.getenv("MODEL_PROVIDER", "openai")),
            )
            table.add_row(
                "Armorer_MODEL",
                env.get("Armorer_MODEL", os.getenv("Armorer_MODEL", "gpt-4o")),
            )

            # Check vault for keys (mask them)
            for p in ["OPENAI", "ANTHROPIC", "GEMINI", "BEDROCK", "OPENROUTER", "OLLAMA"]:
                k = f"{p}_API_KEY"
                if p == "OLLAMA":
                    val = "ollama (internal default)"
                else:
                    val = config_manager.find_vault_key(k)
                    val = "********" if val else "[red]Missing[/red]"
                table.add_row(k, val)

            if env.get("MODEL_PROVIDER") == "ollama":
                table.add_row(
                    "OLLAMA_BASE_URL",
                    env.get("OLLAMA_BASE_URL", "http://localhost:11434/v1/"),
                )

            # Langfuse status
            table.add_section()
            lf_enabled = (
                env.get("LANGFUSE_ENABLED", "false")
            ).lower() == "true"
            table.add_row(
                "LANGFUSE_ENABLED",
                "[green]true[/green]" if lf_enabled else "false",
            )
            lf_pub = config_manager.find_vault_key("LANGFUSE_PUBLIC_KEY")
            table.add_row(
                "LANGFUSE_PUBLIC_KEY",
                "********" if lf_pub else "[red]Not set[/red]",
            )
            lf_sec = config_manager.find_vault_key("LANGFUSE_SECRET_KEY")
            table.add_row(
                "LANGFUSE_SECRET_KEY",
                "********" if lf_sec else "[red]Not set[/red]",
            )
            table.add_row(
                "LANGFUSE_BASE_URL",
                env.get("LANGFUSE_BASE_URL", "https://cloud.langfuse.com"),
            )

            table.add_section()
            dr_enabled = (
                env.get("ARMORER_DAILY_REPORT_ENABLED", "true")
            ).lower() not in {"0", "false", "off", "no"}
            table.add_row(
                "ARMORER_DAILY_REPORT_ENABLED",
                "[green]true[/green]" if dr_enabled else "false",
            )
            table.add_row(
                "ARMORER_DAILY_REPORT_TIME",
                env.get("ARMORER_DAILY_REPORT_TIME", "09:00"),
            )
            table.add_row(
                "ARMORER_DAILY_REPORT_WINDOW_HOURS",
                env.get("ARMORER_DAILY_REPORT_WINDOW_HOURS", "24"),
            )

            cli_common.console.print(table)
