from __future__ import annotations

from pathlib import Path
from typing import Any, Callable, Dict, List, Optional


def parse_key_value_options(values: Optional[List[str]], option_name: str, *, bad_parameter: Callable[[str], Exception]) -> Dict[str, str]:
    parsed: Dict[str, str] = {}
    for raw in values or []:
        text = str(raw or "").strip()
        if not text:
            continue
        if "=" not in text:
            raise bad_parameter(f"{option_name} values must use KEY=VALUE syntax.")
        key, value = text.split("=", 1)
        key = key.strip()
        if not key:
            raise bad_parameter(f"{option_name} values must use KEY=VALUE syntax.")
        parsed[key] = value.strip()
    return parsed


def resolve_configure_target(
    agent: str,
    *,
    registry_cls: Callable[[], Any],
    get_app_with_openclaw_fallback: Callable[[Any, str], Any],
    instances_for_agent: Callable[[Any, str], List[Any]],
    print_agent_not_found: Callable[[str], None],
    console: Any,
    typer_exit: Callable[[int], Exception],
) -> Any:
    registry = registry_cls()
    app_meta = get_app_with_openclaw_fallback(registry, agent)
    if not app_meta:
        matches = instances_for_agent(registry, agent)
        if len(matches) == 1:
            app_meta = matches[0]
        elif len(matches) > 1:
            names = ", ".join(m.name for m in matches)
            console.print(f"[red]Error: Multiple instances found for '{agent}': {names}. Use an alias.[/red]")
            raise typer_exit(1)
        else:
            print_agent_not_found(agent)
            raise typer_exit(1)
    return app_meta


def run_app_configure(
    *,
    agent: str,
    provider: Optional[str],
    model: Optional[str],
    channel: Optional[List[str]],
    skill: Optional[List[str]],
    web_search_provider: Optional[str],
    secret: Optional[List[str]],
    pairing_code: Optional[List[str]],
    skip_bind: bool,
    load_dotenv_fn: Callable[..., Any],
    check_notifications: Callable[[], None],
    resolve_target: Callable[[str], Any],
    infer_agent_id: Callable[[str], str],
    resolve_install_path: Callable[[str], Path],
    parse_key_value: Callable[[Optional[List[str]], str], Dict[str, str]],
    interactive_configure: Callable[[Any, Path], Any],
    prompt_pairing_followup: Callable[[Path, List[str]], Dict[str, Any]],
    apply_config: Callable[..., Dict[str, Any]],
    console: Any,
    typer_exit: Callable[[int], Exception],
) -> None:
    load_dotenv_fn(override=True)
    check_notifications()

    app_meta = resolve_target(agent)
    resolved_agent = str(getattr(app_meta, "agent_id", "") or agent).strip() or str(agent).strip()
    if infer_agent_id(resolved_agent) != "openclaw":
        console.print("[red]Non-interactive app configure is currently supported for OpenClaw instances only.[/red]")
        raise typer_exit(1)

    install_dir = resolve_install_path(app_meta.install_path)
    if not install_dir.exists():
        console.print(f"[red]Error: Install path no longer exists:[/red] {install_dir}")
        raise typer_exit(1)

    secret_map = parse_key_value(secret, "--secret")
    pairing_map = parse_key_value(pairing_code, "--pairing-code")
    requested_channels = [str(v).strip() for v in (channel or []) if str(v).strip()]
    requested_skills = [str(v).strip() for v in (skill or []) if str(v).strip()]
    noninteractive = any(
        [
            provider is not None,
            model is not None,
            web_search_provider is not None,
            channel is not None,
            skill is not None,
            bool(secret_map),
            bool(pairing_map),
            skip_bind,
        ]
    )

    if noninteractive:
        config_patch: Dict[str, Any] = {}
        if provider is not None:
            config_patch["primary_provider"] = str(provider).strip()
        if model is not None:
            config_patch["default_model"] = str(model).strip()
        if channel is not None:
            config_patch["enabled_channels"] = requested_channels
        if skill is not None:
            config_patch["enabled_skills"] = requested_skills
        if web_search_provider is not None:
            config_patch["web_search_provider"] = str(web_search_provider).strip()
        result = apply_config(
            app_meta.name,
            config_patch=config_patch,
            secrets=secret_map,
            bind_channels=not skip_bind,
            pairing_codes=pairing_map,
        )
    else:
        config_state, secrets_map = interactive_configure(app_meta, install_dir)
        bind_channels = str(secrets_map.pop("__ARMORER_BIND_CHANNELS__", "true")).strip().lower() not in {"0", "false", "no", "off"}
        result = apply_config(
            app_meta.name,
            config_patch={
                "primary_provider": config_state.primary_provider,
                "default_model": config_state.default_model,
                "enabled_channels": list(config_state.enabled_channels),
                "enabled_skills": list(config_state.enabled_skills),
                "web_search_provider": config_state.web_search_provider,
            },
            secrets=secrets_map,
            bind_channels=bind_channels,
            pairing_codes={},
        )
        if bind_channels and "telegram" in list(config_state.enabled_channels or []):
            prompt_pairing_followup(install_dir, list(config_state.enabled_channels or []))

    if not result.get("ok"):
        console.print(f"[red]{result.get('message', 'OpenClaw configuration failed.')}[/red]")
        for msg in result.get("errors") or []:
            console.print(f"[red]- {msg}[/red]")
        for msg in result.get("warnings") or []:
            console.print(f"[yellow]- {msg}[/yellow]")
        raise typer_exit(1)

    console.print("[green]OpenClaw is configured inside Armorer.[/green]")
    message = str(result.get("message") or "").strip()
    if message:
        console.print(f"[dim]{message}[/dim]")
    if skip_bind or result.get("channels_bound") is False:
        console.print("[yellow]Channel verification: deferred[/yellow]")
    for msg in result.get("warnings") or []:
        console.print(f"[yellow]- {msg}[/yellow]")
