# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) 2026 sol pbc

"""CLI commands for journal settings management.

Auto-discovered by ``think.call`` and mounted as ``sol call settings ...``.
"""

import json
from typing import Any

import typer

from solstone.convey.reasons import INVALID_CONFIG_VALUE
from solstone.think.convey_client import ConveyClientError, convey_cli, get_client

# Mirrors solstone.apps.settings.routes.API_KEY_ENV_VARS (the canonical order
# used in the "Invalid env var" message); reconstructed here rather than
# imported (HTTP-only gate) or read from the response (Flask sorts JSON keys).
_API_KEY_ENV_VARS = [
    "PLAUD_ACCESS_TOKEN",
]
_SERVICE_KEY_VALIDATION_NAME = {
    "PLAUD_ACCESS_TOKEN": "plaud",
}

app = typer.Typer(
    help="Journal settings — service keys, transcription, identity, and observer."
)

keys_app = typer.Typer(help="API key management.")
app.add_typer(keys_app, name="keys")
transcribe_app = typer.Typer(help="Transcription backend configuration.")
app.add_typer(transcribe_app, name="transcribe")
identity_app = typer.Typer(help="Journal owner identity.")
app.add_typer(identity_app, name="identity")
observer_app = typer.Typer(help="Observer capture settings.")
app.add_typer(observer_app, name="observer")
convey_app = typer.Typer(help="Convey access configuration.")
app.add_typer(convey_app, name="convey")
processing_app = typer.Typer(help="Deferred-processing mode and drain window.")
app.add_typer(processing_app, name="processing")


def _request(
    method: str,
    path: str,
    *,
    params: dict[str, object] | None = None,
    json_body: dict[str, object] | None = None,
) -> Any:
    return get_client().request(method, path, params=params, json=json_body)


def _get_config() -> dict[str, Any]:
    return _request("GET", "/app/settings/api/config")


def _post_config(
    section: str,
    data: dict[str, Any] | None = None,
    *,
    key: str | None = None,
    value: Any = None,
) -> dict[str, Any]:
    body: dict[str, Any] = {"section": section}
    if data is not None:
        body["data"] = data
    if key is not None:
        body["key"] = key
        body["value"] = value
    return _request("POST", "/app/settings/api/config", json_body=body)


def _echo_json(payload: Any) -> None:
    typer.echo(json.dumps(payload, indent=2))


def _exit_with(message: str) -> None:
    typer.echo(message, err=True)
    raise typer.Exit(1)


def _validate_env_var_or_exit(env_var: str) -> None:
    if env_var not in _API_KEY_ENV_VARS:
        _exit_with(
            f"Invalid env var: {env_var}. Must be one of: {', '.join(_API_KEY_ENV_VARS)}"
        )


@processing_app.command("show")
@convey_cli
def processing_show() -> None:
    """Show effective deferred-processing settings."""
    result = _request("GET", "/app/settings/api/processing")
    _echo_json(result)


@processing_app.command("set")
@convey_cli
def processing_set(
    mode: str | None = typer.Option(None, "--mode", help="realtime or deferred."),
    window_start: str | None = typer.Option(
        None,
        "--window-start",
        help="HH:MM drain window start.",
    ),
    window_end: str | None = typer.Option(
        None,
        "--window-end",
        help="HH:MM drain window end.",
    ),
    time_window: bool | None = typer.Option(
        None,
        "--time-window/--no-time-window",
        help="Enable the time-window drain condition.",
    ),
    display_powersave: bool | None = typer.Option(
        None,
        "--display-powersave/--no-display-powersave",
        help="Enable the display-powersave drain condition.",
    ),
) -> None:
    """Set deferred-processing mode and drain window."""
    data: dict[str, Any] = {}
    if mode is not None:
        data["mode"] = mode
    tw: dict[str, Any] = {}
    if window_start is not None:
        tw["start"] = window_start
    if window_end is not None:
        tw["end"] = window_end
    if time_window is not None:
        tw["enabled"] = time_window
    gate: dict[str, Any] = {}
    if tw:
        gate["time_window"] = tw
    if display_powersave is not None:
        gate["display_powersave"] = {"enabled": display_powersave}
    if gate:
        data["gate"] = gate
    if not data:
        _exit_with(
            "error: provide at least one of "
            "--mode/--window-start/--window-end/--time-window/--display-powersave"
        )
    try:
        response = _post_config("processing", data)
    except ConveyClientError as err:
        if err.reason_code == INVALID_CONFIG_VALUE.code and err.detail:
            _exit_with(err.detail)
        raise
    _echo_json(response.get("config", {}).get("processing", {}))


@convey_app.command("status")
@convey_cli
def convey_status() -> None:
    """Show Convey network and dashboard URL status."""

    result = _request("GET", "/app/settings/api/convey/status")
    typer.echo(result["status_text"])


@app.command("show")
@convey_cli
def show() -> None:
    """Show a summary of journal settings."""

    config = _get_config()
    env_config = config.get("env", {})
    keys = {key: bool(env_config.get(key)) for key in _API_KEY_ENV_VARS}
    summary = {
        "identity": config.get("identity", {}),
        "transcribe": config.get("transcribe", {}),
        "observe": config.get("observe", {}),
        "keys": keys,
    }
    _echo_json(summary)


@keys_app.command("show")
@convey_cli
def keys_show() -> None:
    """Show configured API key status."""

    config = _get_config()
    env_config = config.get("env", {})
    status = {key: bool(env_config.get(key)) for key in _API_KEY_ENV_VARS}
    _echo_json(status)


@keys_app.command("set")
@convey_cli
def keys_set(
    env_var: str = typer.Argument(..., help="Environment variable to set."),
    value: str = typer.Argument(..., help="API key value."),
) -> None:
    """Set an API key in journal config."""

    _validate_env_var_or_exit(env_var)
    response = _post_config("env", key=env_var, value=value)
    validation = response.get("key_validation", {}).get(
        _SERVICE_KEY_VALIDATION_NAME.get(env_var, ""),
    )
    _echo_json({"env_var": env_var, "set": True, "validation": validation})


@keys_app.command("clear")
@convey_cli
def keys_clear(
    env_var: str = typer.Argument(..., help="Environment variable to clear."),
) -> None:
    """Clear an API key from journal config."""

    _validate_env_var_or_exit(env_var)
    _post_config("env", key=env_var, value="")
    _echo_json({"env_var": env_var, "cleared": True})


@keys_app.command("validate")
@convey_cli
def keys_validate(
    cache_result: bool = typer.Option(
        False, "--cache-result", help="Persist service-token validation results."
    ),
) -> None:
    """Validate all configured API keys without persisting by default."""

    method = "POST" if cache_result else "GET"
    response = _request(method, "/app/settings/api/validate-keys")
    _echo_json({"key_validation": response.get("key_validation", {})})


@transcribe_app.command("show")
@convey_cli
def transcribe_show() -> None:
    """Show transcription backend configuration."""

    response = _request("GET", "/app/settings/api/transcribe")
    _echo_json(
        {
            "backends": response.get("backends", []),
            "api_keys": response.get("api_keys", {}),
            "config": response.get("config", {}),
        }
    )


@transcribe_app.command("set-backend")
@convey_cli
def transcribe_set_backend(
    backend: str = typer.Argument(..., help="Transcription backend."),
) -> None:
    """Set the transcription backend."""

    response = _request("GET", "/app/settings/api/transcribe")
    valid = sorted(item["name"] for item in response.get("backends", []))
    if backend not in valid:
        _exit_with(f"Invalid backend: {backend}. Must be one of: {', '.join(valid)}")
    update = _post_config("transcribe", {"backend": backend})
    _echo_json(update.get("config", {}).get("transcribe", {}))


@identity_app.command("show")
@convey_cli
def identity_show() -> None:
    """Show journal identity config."""

    config = _get_config()
    _echo_json(config.get("identity", {}))


@identity_app.command("set")
@convey_cli
def identity_set(
    name: str | None = typer.Option(None, "--name"),
    preferred: str | None = typer.Option(None, "--preferred"),
    bio: str | None = typer.Option(None, "--bio"),
    timezone_name: str | None = typer.Option(None, "--timezone"),
    pronouns: str | None = typer.Option(None, "--pronouns"),
    add_email: str | None = typer.Option(None, "--add-email"),
    remove_email: str | None = typer.Option(None, "--remove-email"),
    add_alias: str | None = typer.Option(None, "--add-alias"),
    remove_alias: str | None = typer.Option(None, "--remove-alias"),
) -> None:
    """Update journal owner identity."""

    config = _get_config()
    identity = dict(config.get("identity", {}))
    data: dict[str, Any] = {}

    if name is not None:
        data["name"] = name
    if preferred is not None:
        data["preferred"] = preferred
    if bio is not None:
        data["bio"] = bio
    if timezone_name is not None:
        data["timezone"] = timezone_name

    if pronouns is not None:
        try:
            data["pronouns"] = json.loads(pronouns)
        except json.JSONDecodeError:
            typer.echo("Invalid JSON in pronouns", err=True)
            raise typer.Exit(1)

    if add_email is not None or remove_email is not None:
        emails = list(identity.get("email_addresses", []))
        if add_email is not None and add_email not in emails:
            emails.append(add_email)
        if remove_email is not None:
            emails = [email for email in emails if email != remove_email]
        data["email_addresses"] = emails

    if add_alias is not None or remove_alias is not None:
        aliases = list(identity.get("aliases", []))
        if add_alias is not None and add_alias not in aliases:
            aliases.append(add_alias)
        if remove_alias is not None:
            aliases = [alias for alias in aliases if alias != remove_alias]
        data["aliases"] = aliases

    response = _post_config("identity", data)
    _echo_json(response.get("config", {}).get("identity", {}))


@observer_app.command("show")
@convey_cli
def observer_show() -> None:
    """Show observer configuration with defaults."""

    response = _request("GET", "/app/settings/api/observe")
    _echo_json(response)


@observer_app.command("set")
@convey_cli
def observer_set(
    enabled: bool | None = typer.Option(None, "--enabled/--no-enabled"),
    capture_interval: int | None = typer.Option(None, "--capture-interval"),
) -> None:
    """Update observer capture settings."""

    current = _request("GET", "/app/settings/api/observe")
    defaults = current.get("defaults", {}).get("tmux", {})
    tmux: dict[str, Any] = {}

    if capture_interval is not None:
        min_val = defaults["capture_interval_min"]
        max_val = defaults["capture_interval_max"]
        if capture_interval < min_val or capture_interval > max_val:
            _exit_with(
                "tmux.capture_interval must be an integer between "
                f"{min_val} and {max_val}"
            )
        tmux["capture_interval"] = capture_interval

    if enabled is not None:
        tmux["enabled"] = enabled

    response = _request(
        "POST",
        "/app/settings/api/observe",
        json_body={"tmux": tmux},
    )
    _echo_json(response.get("tmux", {}))
