from __future__ import annotations

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

from armorer.apps.openclaw import OpenClawInstanceConfig


def default_openclaw_model(provider: str) -> str:
    return {
        "openai": "gpt-4o",
        "anthropic": "claude-sonnet-4-20250514",
        "gemini": "google/gemini-3.1-flash-lite-preview",
        "openrouter": "qwen/qwen-2.5-coder-32b-instruct",
        "ollama": "llama3",
    }.get(str(provider or "").strip(), "gpt-4o")


def prompt_secret_with_help(
    label: str,
    key_name: str,
    already_present: bool,
    *,
    ui_confirm: Callable[[str, bool], bool],
    password_prompt: Callable[[str], Optional[str]],
) -> Optional[str]:
    prompt_label = str(label or key_name or "secret").strip()
    if already_present and ui_confirm(f"{prompt_label} is already configured. Keep it?", default=True):
        return None
    secret = password_prompt(f"Enter {prompt_label}:")
    if not secret:
        return None
    return str(secret).strip()


def prepare_openclaw_skills(
    install_dir: Path,
    cfg: Any,
    selected_skills: List[str],
    staged_secrets: Dict[str, str],
    *,
    discover_skills: Callable[[Path], List[Any]],
    check_skill: Callable[..., Any],
) -> tuple[List[str], Dict[str, Dict[str, Any]], Dict[str, str]]:
    selected = [str(v).strip() for v in selected_skills if str(v).strip()]
    skill_catalog = {skill.key: skill for skill in discover_skills(install_dir)}
    enabled: List[str] = []
    skill_settings: Dict[str, Dict[str, Any]] = {}
    warnings: Dict[str, str] = {}

    def env_lookup(name: str) -> Optional[str]:
        return staged_secrets.get(name) or cfg.find_vault_key(name) or cfg.get_key(name)

    for skill_name in selected:
        skill = skill_catalog.get(skill_name)
        if not skill:
            warnings[skill_name] = "unknown skill"
            continue
        result = check_skill(
            skill,
            env_lookup=env_lookup,
            bin_lookup=lambda _name: True,
        )
        if not result.ready:
            parts: List[str] = []
            if result.missing_env:
                parts.append(f"missing credentials: {', '.join(result.missing_env)}")
            if result.missing_bins:
                parts.append(f"missing binaries: {', '.join(result.missing_bins)}")
            warnings[skill_name] = "; ".join(parts) or "skill is not ready"
            continue
        enabled.append(skill_name)
        entry: Dict[str, Any] = {"enabled": True}
        extra_env = {
            env_key: env_lookup(env_key)
            for env_key in getattr(skill, "required_env", [])
            if env_lookup(env_key)
        }
        if extra_env:
            entry["env"] = extra_env
        skill_settings[skill_name] = entry
    return enabled, skill_settings, warnings


def configure_openclaw_instance(
    app_meta: Any,
    install_dir: Path,
    *,
    config_cls: Callable[..., Any],
    load_config: Callable[..., OpenClawInstanceConfig],
    discover_skills: Callable[[Path], List[Any]],
    check_skill: Callable[..., Any],
    prompt_secret: Callable[[str, str, bool], Optional[str]],
    prepare_skills: Callable[[Path, Any, List[str], Dict[str, str]], tuple[List[str], Dict[str, Dict[str, Any]], Dict[str, str]]],
    ui_select: Callable[[str, List[Any], Optional[Any]], Any],
    ui_text: Callable[[str, Optional[str]], Optional[str]],
    ui_checkbox: Callable[[str, List[Any]], Any],
    ui_confirm: Callable[[str, bool], bool],
    ui_choice: Callable[[str, Any], Any],
) -> tuple[OpenClawInstanceConfig, Dict[str, str]]:
    cfg = config_cls(env_path=str(install_dir / ".env"))
    current = load_config(
        install_dir,
        app_meta.app_config_state if isinstance(app_meta.app_config_state, dict) else {},
    )

    providers = ["openai", "anthropic", "gemini", "openrouter", "ollama"]
    channels = ["telegram", "discord", "slack"]
    web_search_choices = ["disabled", "brave", "perplexity", "gemini", "grok", "kimi"]

    provider = ui_select("Model provider:", providers, current.primary_provider)
    if not provider:
        raise SystemExit(1)
    provider = str(provider).strip()

    model_default = str(current.default_model or default_openclaw_model(provider)).strip()
    model = ui_select("Model id:", [model_default, "Enter model id manually"], model_default)
    if model == "Enter model id manually":
        entered_model = (ui_text("Exact model id:", model_default) or "").strip()
        model = entered_model or model_default
    model = str(model or "").strip() or model_default

    web_search_provider = ui_select("Web search provider:", web_search_choices, current.web_search_provider or "disabled")
    if not web_search_provider:
        raise SystemExit(1)
    web_search_provider = str(web_search_provider).strip()

    channel_choices = [
        ui_choice(name, name, checked=name in set(current.enabled_channels or []))
        for name in channels
    ]
    selected_channels = ui_checkbox("Enable chat connections:", channel_choices)
    if selected_channels is None:
        raise SystemExit(1)
    selected_channels = [str(v).strip() for v in selected_channels if str(v).strip()]
    while not selected_channels:
        if ui_confirm("You did not select any chat connections. Continue without enabling any channels?", default=True):
            break
        selected_channels = ui_checkbox("Enable chat connections:", channel_choices)
        if selected_channels is None:
            raise SystemExit(1)
        selected_channels = [str(v).strip() for v in selected_channels if str(v).strip()]

    secrets: Dict[str, str] = {"__ARMORER_BIND_CHANNELS__": "true"}

    provider_key = {
        "openai": "OPENAI_API_KEY",
        "anthropic": "ANTHROPIC_API_KEY",
        "gemini": "GEMINI_API_KEY",
        "openrouter": "OPENROUTER_API_KEY",
        "ollama": "OLLAMA_BASE_URL",
    }.get(provider)
    if provider_key:
        existing_provider_secret = bool(cfg.find_vault_key(provider_key) or cfg.get_key(provider_key))
        replacement = prompt_secret(
            provider_key,
            provider_key,
            existing_provider_secret,
        )
        if replacement:
            secrets[provider_key] = replacement

    channel_secret_map = {
        "telegram": ["TELEGRAM_BOT_TOKEN"],
        "discord": ["DISCORD_BOT_TOKEN"],
        "slack": ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"],
    }
    for channel in selected_channels:
        for key_name in channel_secret_map.get(channel, []):
            existing_channel_secret = bool(
                cfg.get_scoped_secret(app_meta.name, key_name) or cfg.find_vault_key(key_name) or cfg.get_key(key_name)
            )
            replacement = prompt_secret(
                key_name,
                key_name,
                existing_channel_secret,
            )
            if replacement:
                secrets[key_name] = replacement

    skill_catalog = discover_skills(install_dir)
    skill_choices = [
        ui_choice(skill.name or skill.key, skill.key, checked=skill.key in set(current.enabled_skills or []))
        for skill in skill_catalog
    ]
    selected_skills = ui_checkbox("Enable skills:", skill_choices)
    if selected_skills is None:
        raise SystemExit(1)
    selected_skills = [str(v).strip() for v in selected_skills if str(v).strip()]
    enabled_skills, skill_settings, _skill_warnings = prepare_skills(
        install_dir,
        cfg,
        selected_skills,
        secrets,
    )

    config_state = OpenClawInstanceConfig(
        access_mode=current.access_mode,
        primary_provider=provider,
        default_model=model,
        enabled_channels=selected_channels,
        enabled_skills=enabled_skills,
        trusted_proxies=list(current.trusted_proxies or []),
        skill_settings=skill_settings,
        web_search_provider=web_search_provider,
    )
    return config_state, secrets


def prompt_openclaw_pairing_followup(
    install_dir: Path,
    enabled_channels: List[str],
    *,
    discover_pairing_requests: Callable[[Path, str], List[Dict[str, Any]]],
    approve_pairings: Callable[[Path, Dict[str, str]], Dict[str, Any]],
    ui_text: Callable[[str, Optional[str]], Optional[str]],
    ui_confirm: Callable[[str, bool], bool],
) -> Dict[str, Any]:
    channels = [str(v).strip() for v in enabled_channels or [] if str(v).strip()]
    if "telegram" not in channels:
        return {"statuses": {}, "warnings": []}

    pending_requests = discover_pairing_requests(install_dir, "telegram")
    entered = ui_text("Telegram pairing code (blank to detect pending request):", "") or ""
    code = re.sub(r"\s+", "", str(entered)).upper()
    if not code and pending_requests:
        detected = str((pending_requests[0] or {}).get("code") or "").strip().upper()
        if detected and ui_confirm(f"Approve the pending Telegram pairing code {detected} now?", default=True):
            code = detected
    if not code:
        return {"statuses": {"telegram": {"approved": False, "detail": "pairing pending"}}, "warnings": []}
    return approve_pairings(install_dir, {"telegram": code})


def run_openclaw_telegram_pairing(
    agent: str,
    *,
    console: Any,
    stdin_isatty: bool,
    resolve_install_dir: Callable[[str], Optional[Path]],
    compose_service_state: Callable[[Path, str], Tuple[str, bool]],
    discover_pairing_requests: Callable[[Path, str], List[Dict[str, Any]]],
    seed_allowlist: Callable[..., Dict[str, Any]],
    verify_channels: Callable[[Path, List[str]], Dict[str, Any]],
    apply_config: Callable[..., Dict[str, Any]],
    ui_text: Callable[[str, Optional[str]], Optional[str]],
    ui_confirm: Callable[[str, bool], bool],
    parse_allow_ids: Callable[[str], List[str]],
    panel_factory: Callable[..., Any],
) -> None:
    install_dir = resolve_install_dir(agent)
    if install_dir is None or not stdin_isatty:
        return
    gateway_state, gateway_running = compose_service_state(install_dir, "openclaw-gateway")
    if not gateway_running:
        console.print(
            f"[yellow]Telegram pairing cannot continue yet because the OpenClaw gateway is not running (current state: {gateway_state}). Start or repair the gateway first, then retry pairing.[/yellow]"
        )
        return

    pending_requests = discover_pairing_requests(install_dir, "telegram")
    if pending_requests:
        console.print(
            panel_factory(
                "[bold cyan]Telegram Pairing[/bold cyan]\n"
                "A Telegram pairing request is already pending. Paste the code from Telegram to approve it now.",
                border_style="cyan",
                expand=False,
            )
        )
    else:
        console.print(
            panel_factory(
                "[bold cyan]Telegram Pairing[/bold cyan]\n"
                "Send a message to your Telegram bot now. When it gives you a pairing code, paste it here so Armorer can approve it immediately.",
                border_style="cyan",
                expand=False,
            )
        )

    pairing_code = ui_text("Telegram pairing code:", "") or ""
    code = re.sub(r"\s+", "", str(pairing_code)).upper()
    if not code:
        console.print("[yellow]Telegram pairing is still pending. Run Armorer-Managed Setup again when you have the pairing code.[/yellow]")
        return
    matched_request = None
    for request in pending_requests:
        if str(request.get("code") or "").strip().upper() == code:
            matched_request = request
            break

    with console.status("[bold cyan]Approving Telegram pairing...[/bold cyan]"):
        pairing_result = apply_config(
            agent,
            pairing_codes={"telegram": code},
            bind_channels=False,
        )
    pairing_status = ((pairing_result.get("pairing_approval") or {}).get("statuses") or {}).get("telegram") or {}
    if not pairing_status.get("approved"):
        detail = str(pairing_status.get("detail") or "").strip()
        if not detail:
            warnings = pairing_result.get("warnings") or []
            detail = str(warnings[0]).strip() if warnings else "unknown error"
        console.print(f"[yellow]Telegram pairing approval failed:[/yellow] {detail}")
        return

    detected_account_id = str((matched_request or {}).get("account_id") or "").strip()
    auto_seed_result = None
    if detected_account_id:
        extra_ids: List[str] = []
        if ui_confirm("Add more Telegram allowlist IDs now?", default=False):
            raw_extra = ui_text("Additional Telegram ID(s) (comma-separated, blank to skip):", "") or ""
            extra_ids = parse_allow_ids(raw_extra)
        with console.status("[bold cyan]Updating Telegram allowlist...[/bold cyan]"):
            auto_seed_result = seed_allowlist(
                install_dir,
                account_id=detected_account_id,
                extra_ids=extra_ids,
            )
        for warning in auto_seed_result.get("warnings") or []:
            console.print(f"[yellow]{warning}[/yellow]")
        if auto_seed_result.get("ok"):
            seeded_ids = [str(v).strip() for v in auto_seed_result.get("seeded_ids") or [] if str(v).strip()]
            allow_from = [str(v).strip() for v in auto_seed_result.get("allow_from") or [] if str(v).strip()]
            if seeded_ids:
                console.print(f"[green]✓ Added Telegram allowlist ID(s):[/green] {', '.join(seeded_ids)}")
            if allow_from:
                console.print(f"[dim]Telegram allowFrom now includes:[/dim] {', '.join(allow_from)}")
    else:
        console.print(
            "[yellow]Telegram pairing was approved, but OpenClaw did not provide a sender account ID for automatic allowlist seeding.[/yellow]\n"
            "[dim]This usually means the pending pairing request did not include an `account_id` field, so Armorer can't infer your Telegram numeric user ID automatically.[/dim]"
        )
        console.print(
            "[dim]\nHow to find your Telegram numeric ID:\n"
            "- Message [bold]@userinfobot[/bold] in Telegram and copy the numeric ID it shows.\n"
            "- Or message your bot and, if it supports it, use a command like [bold]/id[/bold] (bot-specific).\n"
            "- Paste one or more IDs below (comma-separated). You can leave it blank to skip and configure later.\n[/dim]"
        )
        if ui_confirm("Add Telegram allowlist IDs manually now?", default=True):
            raw_ids = ui_text("Telegram numeric ID(s) to allow (comma-separated, blank to skip):", "") or ""
            manual_ids = parse_allow_ids(raw_ids)
            if manual_ids:
                with console.status("[bold cyan]Updating Telegram allowlist...[/bold cyan]"):
                    auto_seed_result = seed_allowlist(
                        install_dir,
                        account_id=manual_ids[0],
                        extra_ids=manual_ids[1:],
                    )
                for warning in auto_seed_result.get("warnings") or []:
                    console.print(f"[yellow]{warning}[/yellow]")
                if auto_seed_result.get("ok"):
                    allow_from = [str(v).strip() for v in auto_seed_result.get("allow_from") or [] if str(v).strip()]
                    if allow_from:
                        console.print(f"[dim]Telegram allowFrom now includes:[/dim] {', '.join(allow_from)}")

    with console.status("[bold cyan]Verifying Telegram...[/bold cyan]"):
        channel_verify = verify_channels(install_dir, ["telegram"])
    telegram_status = (channel_verify.get("statuses") or {}).get("telegram") or {}
    if telegram_status.get("verified"):
        console.print("[green]✓ Telegram is paired and verified.[/green]")
        return

    detail = str(telegram_status.get("detail") or "").strip() or "verification is still pending"
    console.print(f"[yellow]Telegram pairing was approved, but verification is not complete yet:[/yellow] {detail}")


def run_armorer_managed_openclaw_setup(
    agent: str,
    *,
    console: Any,
    ui_select: Callable[[str, List[Any], Optional[Any]], Any],
    ui_text: Callable[[str, Optional[str]], Optional[str]],
    ui_checkbox: Callable[[str, List[Any]], Any],
    ui_confirm: Callable[[str, bool], bool],
    ui_choice: Callable[[str, Any], Any],
    ui_choice_value: Callable[[Any], Any],
    truncate_menu_text: Callable[[str, int], str],
    normalize_provider_id: Callable[[str], str],
    discovered_catalog_loader: Callable[[str], Tuple[List[Dict[str, Any]], Optional[str]]],
    discovered_provider_ids: Callable[[List[Dict[str, Any]]], List[str]],
    provider_model_loader: Callable[[str, str], Tuple[List[Dict[str, Any]], Optional[str]]],
    model_choice_builder: Callable[[str, Dict[str, Any]], Optional[Any]],
    get_setup_context: Callable[[str], Dict[str, Any]],
    validate_selection: Callable[..., Dict[str, Any]],
    apply_config: Callable[..., Dict[str, Any]],
    telegram_pairing_runner: Callable[[str], None],
    password_prompt: Callable[[str], Optional[str]],
) -> int:
    with console.status("[bold cyan]Loading OpenClaw setup...[/bold cyan]"):
        context = get_setup_context(agent)
    if not context.get("ok"):
        console.print(f"[red]{context.get('message', 'OpenClaw setup is unavailable.')}[/red]")
        return 1

    current = dict(context.get("current_config") or {})
    options = dict(context.get("options") or {})
    provider_defaults = dict(options.get("provider_default_models") or {})
    credentials = dict(context.get("credentials") or {})
    channel_state = dict((context.get("state_summary") or {}).get("channel_state") or {})
    current_enabled_skills = set(str(v) for v in (current.get("enabled_skills") or []) if str(v).strip())

    current_provider = normalize_provider_id(str(current.get("primary_provider") or "").strip())
    with console.status("[bold cyan]Loading OpenClaw providers...[/bold cyan]"):
        discovered_catalog, catalog_error = discovered_catalog_loader(agent)
    if catalog_error:
        console.print(f"[yellow]{catalog_error}[/yellow]")
    provider_choices = [
        normalize_provider_id(provider)
        for provider in discovered_provider_ids(discovered_catalog)
    ]
    provider_choices = list(dict.fromkeys([provider for provider in provider_choices if provider]))
    if not provider_choices:
        provider_choices = [str(v) for v in options.get("providers") or [] if str(v).strip()]
    if current_provider and current_provider not in provider_choices:
        provider_choices = [current_provider, *provider_choices]
    current_provider = str(current_provider or provider_choices[0] if provider_choices else "openai")
    provider_defaulted = False
    provider = (
        ui_select("Model provider:", provider_choices, current_provider)
        if provider_choices
        else current_provider
    )
    # Be tolerant to test harnesses / non-standard ui_select implementations
    # that may return values outside the offered choice set.
    if provider_choices and provider not in provider_choices:
        provider_defaulted = True
        provider = current_provider
    if not provider:
        return 1

    model_default = str(provider_defaults.get(provider) or current.get("default_model") or "").strip()
    current_model = str(current.get("default_model") or "").strip()
    discovered_models = []
    if discovered_catalog and not catalog_error:
        for entry in discovered_catalog:
            key = str(entry.get("key") or "").strip()
            if "/" not in key:
                continue
            raw_provider, _ = key.split("/", 1)
            if normalize_provider_id(raw_provider) != provider:
                continue
            discovered_models.append(entry)
    if not discovered_models:
        with console.status(f"[bold cyan]Loading models for {provider}...[/bold cyan]"):
            discovered_models, discovery_error = provider_model_loader(agent, provider)
        if discovery_error:
            console.print(f"[yellow]{discovery_error}[/yellow]")
    else:
        discovery_error = None
    if not discovered_models:
        console.print(
            f"[yellow]OpenClaw did not return any discovered models for provider '{provider}'. Enter the exact model id manually if you want to continue.[/yellow]"
        )

    model_choices: List[Any] = []
    seen_model_ids = set()
    if not discovered_models and current_provider == provider and current_model:
        current_choice = ui_choice(
            truncate_menu_text(f"{current_model}  current selection", 110),
            current_model,
        )
        model_choices.append(current_choice)
        seen_model_ids.add(current_model)
    for entry in discovered_models:
        choice = model_choice_builder(provider, entry)
        if not choice:
            continue
        model_id = str(ui_choice_value(choice) or "").strip()
        if not model_id or model_id in seen_model_ids:
            continue
        model_choices.append(choice)
        seen_model_ids.add(model_id)
    if not discovered_models and model_default and model_default not in seen_model_ids:
        model_choices.append(
            ui_choice(
                truncate_menu_text(f"{model_default}  recommended default", 110),
                model_default,
            )
        )
        seen_model_ids.add(model_default)
    model_choices.append(ui_choice("Enter model id manually", "__manual__"))

    selected_model = ui_select(
        "Model id:",
        model_choices,
        ui_choice_value(model_choices[0]) if model_choices else None,
    ) if model_choices else None
    # Same tolerance as provider selection: if ui_select returns a value that
    # isn't one of the offered choices, fall back to the first non-manual
    # option to avoid triggering interactive manual entry.
    if model_choices:
        valid_model_ids = {ui_choice_value(c) for c in model_choices}
        if selected_model not in valid_model_ids:
            # Pick first choice (which should be current/recommended model).
            selected_model = ui_choice_value(model_choices[0])
    if selected_model == "__manual__":
        manual_default = current_model or model_default
        model = (ui_text("Exact model id:", manual_default) or "").strip()
    else:
        model = str(selected_model or "").strip()
    if not model:
        console.print("[red]A model id is required.[/red]")
        return 1

    provider_key_map = {
        "openai": "OPENAI_API_KEY",
        "anthropic": "ANTHROPIC_API_KEY",
        "gemini": "GEMINI_API_KEY",
        "openrouter": "OPENROUTER_API_KEY",
        "ollama": "OLLAMA_BASE_URL",
    }
    provider_key = provider_key_map.get(provider, "")
    provider_key_present = bool(provider_key and credentials.get(provider_key, {}).get("present"))
    secrets: Dict[str, str] = {}
    if provider_key:
        if provider_key_present:
            keep_existing = ui_confirm(f"{provider_key} is already configured. Keep it?", default=True)
            if not keep_existing:
                replacement = password_prompt(f"Enter {provider_key}:")
                if not replacement:
                    console.print(f"[red]{provider_key} is required.[/red]")
                    return 1
                secrets[provider_key] = str(replacement).strip()
        else:
            provided = password_prompt(f"Enter {provider_key}:")
            if not provided:
                console.print(f"[red]{provider_key} is required.[/red]")
                return 1
            secrets[provider_key] = str(provided).strip()

    channel_prompt_labels = {
        "TELEGRAM_BOT_TOKEN": "Telegram bot token",
        "DISCORD_BOT_TOKEN": "Discord bot token",
        "SLACK_BOT_TOKEN": "Slack bot token",
        "SLACK_APP_TOKEN": "Slack app token",
    }
    enabled_channels: List[str] = []
    for channel in [str(v) for v in options.get("channels") or [] if str(v).strip()]:
        currently_enabled = bool(channel_state.get(channel, {}).get("enabled"))
        if ui_confirm(f"Enable {channel}?", default=currently_enabled):
            enabled_channels.append(channel)
            required_keys = [str(v).strip() for v in channel_state.get(channel, {}).get("required_keys", []) if str(v).strip()]
            present_keys = set(str(v).strip() for v in channel_state.get(channel, {}).get("present_keys", []) if str(v).strip())
            for key_name in required_keys:
                prompt_label = channel_prompt_labels.get(key_name, key_name)
                if key_name in present_keys:
                    keep_existing = ui_confirm(f"{prompt_label} is already configured. Keep it?", default=True)
                    if keep_existing:
                        continue
                token = password_prompt(f"Enter {prompt_label}:")
                if not token:
                    console.print(f"[red]{key_name} is required to enable {channel}.[/red]")
                    return 1
                secrets[str(key_name)] = str(token).strip()

    enabled_skills: List[str] = []
    skill_choices = []
    for skill in options.get("skills") or []:
        if not isinstance(skill, dict):
            continue
        key = str(skill.get("key") or "").strip()
        if not key:
            continue
        name = str(skill.get("name") or key).strip()
        description = str(skill.get("description") or "").strip()
        ready = bool(skill.get("ready"))
        suffix = "ready" if ready else "needs setup"
        title = f"{name}  ({suffix})"
        if description:
            title += f"  {description}"
        skill_choices.append(
            ui_choice(
                truncate_menu_text(title, 110),
                key,
                checked=key in current_enabled_skills,
            )
        )
    if skill_choices:
        selected_skills = ui_checkbox("Enable skills:", skill_choices)
        if selected_skills is None:
            return 1
        enabled_skills = [str(v).strip() for v in selected_skills if str(v).strip()]
        skill_meta = {
            str(skill.get("key") or "").strip(): skill
            for skill in (options.get("skills") or [])
            if isinstance(skill, dict)
        }
        for skill_name in enabled_skills:
            skill = skill_meta.get(skill_name, {})
            missing_env = [str(v).strip() for v in skill.get("missing_env") or [] if str(v).strip()]
            for key_name in missing_env:
                if key_name in secrets:
                    continue
                if ui_confirm(f"{skill_name} needs {key_name}. Enter it now?", default=True):
                    token = password_prompt(f"Enter {key_name}:")
                    if not token:
                        console.print(f"[red]{key_name} is required to enable {skill_name}.[/red]")
                        return 1
                    secrets[key_name] = str(token).strip()

    with console.status("[bold cyan]Validating OpenClaw setup...[/bold cyan]"):
        validation = validate_selection(
            agent,
            config_patch={
                "primary_provider": provider,
                "default_model": model,
                "enabled_channels": enabled_channels,
                "enabled_skills": enabled_skills,
            },
            secrets=secrets,
        )
    if not validation.get("ok"):
        console.print(f"[red]{validation.get('message', 'OpenClaw setup validation failed.')}[/red]")
        for msg in validation.get("errors") or []:
            console.print(f"[red]- {msg}[/red]")
        for msg in validation.get("warnings") or []:
            console.print(f"[yellow]- {msg}[/yellow]")
        for item in validation.get("missing_credentials") or []:
            key_name = str((item or {}).get("key") or "").strip()
            if key_name:
                console.print(f"[yellow]- Missing credential: {key_name}[/yellow]")
        return 1

    with console.status("[bold cyan]Applying OpenClaw setup...[/bold cyan]"):
        result = apply_config(
            agent,
            config_patch={
                "primary_provider": provider,
                "default_model": model,
                "enabled_channels": enabled_channels,
                "enabled_skills": enabled_skills,
            },
            secrets=secrets,
            bind_channels=True,
        )
    if not result.get("ok"):
        console.print(f"[red]{result.get('message', 'OpenClaw setup 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]")
        return 1

    console.print(f"[green]{result.get('message', 'Applied OpenClaw configuration.')}[/green]")
    for msg in result.get("warnings") or []:
        console.print(f"[yellow]- {msg}[/yellow]")
    if "telegram" in enabled_channels:
        telegram_status = ((result.get("channel_verification") or {}).get("statuses") or {}).get("telegram") or {}
        telegram_verified = bool(telegram_status.get("verified"))
        telegram_linked = bool(telegram_status.get("linked"))
        if not telegram_verified or not telegram_linked:
            telegram_pairing_runner(agent)
    return 0
