"""Ambient CLI — manage worker daemon and coordinator."""

from __future__ import annotations

import click

from hive_compute.model_ids import SUPPORTED_QUANTIZATIONS


@click.group()
def main():
    """Ambient — distributed transcription on idle Macs."""
    pass


@main.command()
@click.option("--coordinator-url", default="https://hivecompute.fly.dev", help="Coordinator URL")
@click.option("--enrollment-token", default=None, help="Optional enrollment token for tracking")
@click.option("--worker-id", default=None, help="Custom worker ID (defaults to hostname)")
def init(coordinator_url: str, enrollment_token: str | None, worker_id: str | None):
    """Initialize worker — register with coordinator and save config."""
    import platform
    from pathlib import Path

    import httpx
    import yaml

    from hive_compute.model_ids import DEFAULT_SUPPORTED_TRANSCRIPTION_MODELS
    from hive_compute.worker.idle_detector import get_hardware_info
    from hive_compute.worker.keychain import store_worker_token

    if not worker_id:
        worker_id = platform.node()

    hw = get_hardware_info()

    transcribe_handler = {
        "backend": "mlx-whisper",
        "model": "large-v3",
        "quantization": "4bit",
        "language": "en",
        "prewarm_model_on_startup": True,
        "supported_models": list(DEFAULT_SUPPORTED_TRANSCRIPTION_MODELS),
    }
    advertised_models = list(DEFAULT_SUPPORTED_TRANSCRIPTION_MODELS)

    # Register with coordinator
    click.echo(f"Registering worker '{worker_id}' with {coordinator_url}...")
    headers = _registration_headers(coordinator_url, worker_id, enrollment_token)
    try:
        resp = httpx.post(
            f"{coordinator_url.rstrip('/')}/api/v1/workers/register",
            headers=headers,
            json={
                "worker_id": worker_id,
                "chip": hw.get("chip"),
                "memory_gb": hw.get("memory_gb"),
                "available_handlers": ["transcribe"],
                "available_models": advertised_models,
            },
            timeout=10,
        )
        resp.raise_for_status()
        data = resp.json()
        worker_token = data["worker_token"]
        click.echo(f"Registered: {data['message']}")
    except httpx.HTTPStatusError as e:
        click.echo(
            f"Registration failed: {_format_registration_failure(e, headers=headers)}",
            err=True,
        )
        raise SystemExit(1) from e
    except Exception as e:
        click.echo(f"Registration failed: {e}", err=True)
        raise SystemExit(1) from e

    try:
        worker_token_keychain = store_worker_token(worker_token, coordinator_url, worker_id)
    except Exception as e:
        click.echo(f"Failed to store worker token in macOS Keychain: {e}", err=True)
        raise SystemExit(1) from e

    # Write config
    config_dir = Path("~/.ambient").expanduser()
    config_dir.mkdir(parents=True, exist_ok=True)
    config_path = config_dir / "config.yaml"

    config = {
        "coordinator_url": coordinator_url,
        "worker_id": worker_id,
        "worker_token_keychain": worker_token_keychain,
        "handlers": {
            "transcribe": transcribe_handler,
        },
        "idle_detection": {
            "min_idle_minutes": 5,
            "require_ac_power": True,
            "check_thermal": True,
        },
        "polling": {
            "interval_seconds": 30,
            "busy_interval_seconds": 5,
            "idle_interval_seconds": 60,
        },
    }

    with open(config_path, "w") as f:
        yaml.dump(config, f, default_flow_style=False)

    click.echo(f"Config written to {config_path}")
    click.echo(f"Worker ID: {worker_id}")
    click.echo("Worker token stored in macOS Keychain")
    click.echo("Run 'hive install-service' to run as a background service, or 'hive start' for a foreground session")


def _registration_headers(
    coordinator_url: str,
    worker_id: str,
    enrollment_token: str | None,
) -> dict[str, str]:
    if enrollment_token:
        return {"X-Enrollment-Token": enrollment_token}

    persisted = _load_existing_worker_token(coordinator_url, worker_id)
    if persisted:
        return {"Authorization": f"Bearer {persisted}"}

    return {}


def _format_registration_failure(exc, *, headers: dict[str, str]) -> str:
    status_code = exc.response.status_code
    used_enrollment_token = "X-Enrollment-Token" in headers
    used_existing_token = "Authorization" in headers

    if status_code == 401 and not used_enrollment_token and not used_existing_token:
        return (
            "This coordinator requires --enrollment-token for first-time registration. "
            "Create one in the manager dashboard and rerun this command."
        )
    if status_code in {401, 403} and used_enrollment_token:
        return "The provided --enrollment-token is invalid or expired."

    detail = _extract_error_detail(exc.response)
    return detail or str(exc)


def _extract_error_detail(response) -> str:
    try:
        payload = response.json()
    except ValueError:
        return response.text.strip()

    if isinstance(payload, dict):
        for key in ("detail", "error", "message"):
            value = payload.get(key)
            if isinstance(value, str) and value.strip():
                return value.strip()
    return ""


def _load_existing_worker_token(coordinator_url: str, worker_id: str) -> str | None:
    try:
        from hive_compute.worker.keychain import build_worker_token_ref, load_worker_token

        ref = build_worker_token_ref(coordinator_url, worker_id)
        return load_worker_token(ref)
    except Exception:
        return None


@main.command()
@click.option("--coordinator-url", default="https://hivecompute.fly.dev", help="Coordinator URL")
@click.option("--enrollment-token", default=None, help="Optional enrollment token for tracking")
@click.option("--worker-id", default=None, help="Custom worker ID (defaults to hostname)")
@click.option("--skip-service", is_flag=True, help="Skip LaunchAgent install (init only)")
@click.option(
    "--accept-worker-agreement",
    is_flag=True,
    help="Skip the interactive worker agreement prompt for scripted installs",
)
def onboard(
    coordinator_url: str,
    enrollment_token: str | None,
    worker_id: str | None,
    skip_service: bool,
    accept_worker_agreement: bool,
):
    """One-step worker setup: register, store credentials, install background service."""
    from hive_compute.worker.launchd import install_launch_agent

    agreement_url = f"{coordinator_url.rstrip('/')}/worker-agreement"
    _confirm_worker_agreement(agreement_url, accepted_via_flag=accept_worker_agreement)

    # Run init via the Click context so all init logic is reused
    ctx = click.get_current_context()
    ctx.invoke(init, coordinator_url=coordinator_url, enrollment_token=enrollment_token, worker_id=worker_id)

    if skip_service:
        click.echo("Background service install skipped. Run 'hive install-service' when ready.")
        return

    config = _load_config_file("~/.ambient/config.yaml")
    resolved_worker_id = config.get("worker_id")
    if not resolved_worker_id:
        click.echo("Config is missing worker_id after init. Run 'hive init' again.", err=True)
        raise SystemExit(1)

    try:
        label, plist_path = install_launch_agent(resolved_worker_id, "~/.ambient/config.yaml")
    except Exception as e:
        click.echo(f"Failed to install LaunchAgent: {e}", err=True)
        click.echo("Worker registered successfully. Run 'hive install-service' manually.")
        raise SystemExit(1) from e

    click.echo(f"Installed LaunchAgent: {label}")
    click.echo(f"Plist path:           {plist_path}")
    click.echo("The worker will launch at login and stay running in the background.")
    click.echo("Run 'hive status' to verify the saved worker configuration.")


def _confirm_worker_agreement(agreement_url: str, *, accepted_via_flag: bool) -> None:
    click.echo(f"By installing, you agree to our Worker Agreement: {agreement_url}")
    if accepted_via_flag:
        click.echo("Worker Agreement accepted via --accept-worker-agreement.")
        return
    click.prompt("Press Enter to accept, or Ctrl-C to cancel", prompt_suffix="", default="", show_default=False)


@main.group("config")
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
@click.pass_context
def config_group(ctx: click.Context, config_path: str):
    """Inspect and update the worker config file."""
    ctx.ensure_object(dict)
    ctx.obj["config_path"] = config_path


@config_group.command("show")
@click.pass_context
def config_show(ctx: click.Context):
    """Pretty-print the current worker configuration."""
    import yaml

    config_path = ctx.obj["config_path"]
    config = _load_config_file(config_path)
    rendered = yaml.safe_dump(_sanitize_config_for_display(config), sort_keys=False, default_flow_style=False).rstrip()
    click.echo(rendered)


@config_group.command("set")
@click.argument("key")
@click.argument("value")
@click.option(
    "--quantization",
    type=click.Choice(list(SUPPORTED_QUANTIZATIONS), case_sensitive=False),
    default=None,
    help="Also update handlers.transcribe.quantization when setting the model shorthand.",
)
@click.pass_context
def config_set(ctx: click.Context, key: str, value: str, quantization: str | None):
    """Update a single worker config field with validation."""
    import yaml

    from hive_compute.worker.config_model import update_worker_config_value

    config_path = ctx.obj["config_path"]
    config = _load_config_file(config_path)
    quantization_key = None
    quantization_value = None

    try:
        updated, resolved_key, parsed_value = update_worker_config_value(config, key, value)
        if quantization is not None:
            if key.strip() not in {"model", "handlers.transcribe.model"}:
                click.echo("--quantization can only be used when setting the model.", err=True)
                raise SystemExit(1)
            updated, quantization_key, quantization_value = update_worker_config_value(
                updated,
                "quantization",
                quantization,
            )
    except ValueError as exc:
        click.echo(str(exc), err=True)
        raise SystemExit(1) from exc

    if updated == config:
        click.echo(f"No change: {resolved_key} is already set to {parsed_value!r}")
        return

    _write_config_file(config_path, updated)
    rendered_value = yaml.safe_dump(parsed_value, default_flow_style=False).strip()
    click.echo(f"Updated {resolved_key} = {rendered_value}")
    if quantization_key is not None:
        rendered_quantization = yaml.safe_dump(quantization_value, default_flow_style=False).strip()
        click.echo(f"Updated {quantization_key} = {rendered_quantization}")
    click.echo("Config changes take effect on the next poll cycle.")


@main.group("models")
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
@click.pass_context
def models_group(ctx: click.Context, config_path: str):
    """Inspect and prefetch worker transcription models."""
    ctx.ensure_object(dict)
    ctx.obj["config_path"] = config_path


@models_group.command("list")
@click.pass_context
def models_list(ctx: click.Context):
    """Show configured model state for the worker backend."""
    handler = _load_transcribe_handler(ctx.obj["config_path"])
    capabilities = handler.model_capabilities()

    if not capabilities:
        click.echo("No transcription models are configured.")
        return

    for capability in capabilities:
        states = [state for state in ("warmed", "installed", "downloadable") if capability.get(state)]
        rendered_states = ",".join(states) if states else capability.get("state", "unknown")
        prefix = "*" if capability.get("primary") else "-"
        click.echo(
            f"{prefix} {capability['model']}: state={capability['state']} "
            f"flags={rendered_states} backend={capability['backend']} repo={capability.get('repo') or '-'}"
        )


@models_group.command("pull")
@click.argument("model")
@click.option(
    "--quantization",
    type=click.Choice(list(SUPPORTED_QUANTIZATIONS), case_sensitive=False),
    default=None,
    help="Override quantization when the model shorthand does not include it.",
)
@click.pass_context
def models_pull(ctx: click.Context, model: str, quantization: str | None):
    """Pre-download a transcription model through the configured backend."""
    handler = _load_transcribe_handler(ctx.obj["config_path"])
    result = handler.pull_model(model, quantization=quantization)
    status = result.get("status")

    if status not in {"warmed", "already_warm"}:
        click.echo(
            f"Failed to pull {result.get('model', model)}: {result.get('error', 'unknown error')}",
            err=True,
        )
        raise SystemExit(1)

    click.echo(
        f"Model {result['model']} is {status.replace('_', ' ')} "
        f"via {result['backend']} ({result.get('elapsed_seconds', 0):.3f}s)"
    )


@main.command()
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
@click.option("--no-idle-check", is_flag=True, help="Disable idle detection (for testing)")
def start(config_path: str, no_idle_check: bool):
    """Start the worker daemon."""
    from hive_compute.worker.daemon import WorkerDaemon

    runtime_overrides = None
    if no_idle_check:
        click.echo("Idle detection disabled — processing all jobs immediately")
        runtime_overrides = {
            "idle_detection": {
                "min_idle_minutes": 0,
                "require_ac_power": False,
                "check_thermal": False,
            }
        }

    daemon = WorkerDaemon(config_path, runtime_overrides=runtime_overrides)
    daemon.run()


@main.command()
@click.option("--host", default="0.0.0.0", help="Bind host")
@click.option("--port", default=8080, type=int, help="Bind port")
def serve(host: str, port: int):
    """Start the coordinator server."""
    import uvicorn

    click.echo(f"Starting Ambient coordinator on {host}:{port}")
    uvicorn.run(
        "hive_compute.coordinator.app:create_app",
        host=host,
        port=port,
        factory=True,
    )


@main.command("doctor-coordinator")
def doctor_coordinator():
    """Run preflight diagnostics for the coordinator environment."""
    from hive_compute.coordinator.diagnostics import run_coordinator_doctor

    checks = run_coordinator_doctor()
    failures = 0
    warnings = 0
    labels = {"ok": "OK", "warn": "WARN", "fail": "FAIL"}

    for check in checks:
        if check.status == "fail":
            failures += 1
        elif check.status == "warn":
            warnings += 1

        click.echo(f"[{labels[check.status]}] {check.name:<15} {check.summary}")
        if check.detail:
            click.echo(f"       {check.detail}")

    if failures:
        click.echo(f"Coordinator doctor found {failures} failure(s) and {warnings} warning(s).", err=True)
        raise SystemExit(1)

    if warnings:
        click.echo(f"Coordinator doctor found {warnings} warning(s).")
    else:
        click.echo("Coordinator doctor found no issues.")


@main.command()
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
@click.option("--audio", "audio_path", default=None, help="Audio clip to benchmark (defaults to bundled corpus)")
@click.option(
    "--models",
    default=None,
    help="Comma-separated models to benchmark (defaults to current config model plus standard baselines)",
)
@click.option(
    "--output-dir",
    default="~/.ambient/benchmarks",
    help="Directory where benchmark JSON and Markdown reports are written",
)
@click.option("--report-to-coordinator", is_flag=True, help="Upload results to the configured coordinator")
def benchmark(
    config_path: str,
    audio_path: str | None,
    models: str | None,
    output_dir: str,
    report_to_coordinator: bool,
):
    """Benchmark local inference speed for the configured transcription backend."""
    from hive_compute.worker.api_client import build_coordinator_client
    from hive_compute.worker.benchmark import (
        bundled_benchmark_fixtures,
        resolve_benchmark_models,
        run_benchmark_suite,
    )
    from hive_compute.worker.config_model import load_worker_config

    config = load_worker_config(config_path).model_dump()
    transcribe = config.get("handlers", {}).get("transcribe")
    if not transcribe:
        click.echo("Config is missing handlers.transcribe. Run 'hive init' again.", err=True)
        raise SystemExit(1)

    benchmark_models = resolve_benchmark_models(transcribe, models=models)
    if audio_path:
        report = run_benchmark_suite(
            transcribe,
            audio_path=audio_path,
            models=benchmark_models,
            output_dir=output_dir,
        )
    else:
        with bundled_benchmark_fixtures() as fixtures:
            report = run_benchmark_suite(
                transcribe,
                audio_path=None,
                models=benchmark_models,
                output_dir=output_dir,
                fixtures=fixtures,
            )
    click.echo(report["markdown"].rstrip())
    click.echo(f"Saved JSON:      {report['json_path']}")
    click.echo(f"Saved Markdown:  {report['markdown_path']}")
    if report_to_coordinator:
        client = build_coordinator_client(config)
        if client.report_benchmark(report):
            click.echo("Reported benchmark results to coordinator.")
        else:
            click.echo("Failed to report benchmark results to coordinator.", err=True)
            raise SystemExit(1)


@main.command("benchmark-whisper")
@click.option("--audio-file", type=click.Path(exists=True, dir_okay=False, path_type=str), required=True)
@click.option("--reference-text", required=True, help="Ground-truth transcript used for WER")
@click.option("--language", default="en", show_default=True)
@click.option("--repeats", default=2, show_default=True, type=int)
@click.option("--case", "requested_cases", multiple=True, help="Built-in benchmark case to run")
@click.option("--all-builtins", is_flag=True, help="Run every built-in benchmark case")
@click.option("--repo-case", "repo_cases", multiple=True, help="Custom MLX case in NAME=HF_REPO format")
@click.option("--cpp-case", "cpp_cases", multiple=True, help="Custom whisper.cpp case in NAME=/path/model.bin format")
@click.option("--json-output", type=click.Path(dir_okay=False, path_type=str), default=None)
@click.option("--markdown-output", type=click.Path(dir_okay=False, path_type=str), default=None)
def benchmark_whisper(
    audio_file: str,
    reference_text: str,
    language: str,
    repeats: int,
    requested_cases: tuple[str, ...],
    all_builtins: bool,
    repo_cases: tuple[str, ...],
    cpp_cases: tuple[str, ...],
    json_output: str | None,
    markdown_output: str | None,
):
    """Benchmark local Whisper runtimes on this Mac."""
    from pathlib import Path

    from hive_compute.worker.benchmark import (
        format_results_table,
        parse_case_names,
        run_benchmark_suite,
        write_json_report,
        write_markdown_report,
    )

    try:
        cases = parse_case_names(
            list(requested_cases),
            all_builtins=all_builtins,
            repo_cases=list(repo_cases),
            cpp_cases=list(cpp_cases),
        )
    except ValueError as exc:
        click.echo(str(exc), err=True)
        raise SystemExit(2) from exc

    hardware, results = run_benchmark_suite(
        audio_path=Path(audio_file).expanduser().resolve(),
        reference_text=reference_text,
        language=language,
        repeats=repeats,
        cases=cases,
    )

    click.echo(format_results_table(results))
    if json_output:
        write_json_report(Path(json_output), hardware, results)
    if markdown_output:
        write_markdown_report(Path(markdown_output), hardware, results)

    if any(result.status == "error" for result in results) or all(result.status == "skipped" for result in results):
        raise SystemExit(1)


@main.command("backup-db")
@click.option("--database", "database_path", default=None, help="SQLite database path")
@click.option(
    "--output",
    "output_path",
    default=None,
    help="Backup file path (defaults to ./backups/<timestamp>.sqlite3)",
)
@click.option("--backup-dir", default=None, help="Directory for generated backups when --output is omitted")
def backup_db(database_path: str | None, output_path: str | None, backup_dir: str | None):
    """Create a consistent SQLite backup for the coordinator database."""
    from hive_compute.config import get_settings
    from hive_compute.ops.sqlite_backup import backup_sqlite_database, default_backup_output_path

    resolved_database = database_path or get_settings().database_path
    resolved_output = output_path or str(default_backup_output_path(resolved_database, backup_dir=backup_dir))

    try:
        result = backup_sqlite_database(resolved_database, resolved_output)
    except FileNotFoundError as exc:
        click.echo(str(exc), err=True)
        raise SystemExit(1) from exc
    except Exception as exc:
        click.echo(f"Backup failed: {exc}", err=True)
        raise SystemExit(1) from exc

    click.echo(f"Database:   {result.source_path}")
    click.echo(f"Backup:     {result.output_path}")
    click.echo(f"Size bytes: {result.size_bytes}")


@main.command("restore-db")
@click.option("--database", "database_path", default=None, help="SQLite database path")
@click.option("--input", "backup_path", required=True, help="Backup file to restore from")
@click.option("--snapshot", "snapshot_path", default=None, help="Optional pre-restore snapshot path")
@click.option("--snapshot-dir", default=None, help="Directory for the generated pre-restore snapshot")
@click.option("--force", is_flag=True, help="Acknowledge that this overwrites the target database")
def restore_db(
    database_path: str | None,
    backup_path: str,
    snapshot_path: str | None,
    snapshot_dir: str | None,
    force: bool,
):
    """Restore the coordinator SQLite database from a backup file."""
    from hive_compute.config import get_settings
    from hive_compute.ops.sqlite_backup import default_restore_snapshot_path, restore_sqlite_database

    if not force:
        click.echo(
            "Refusing to overwrite the database without --force. Stop the coordinator first and retry.",
            err=True,
        )
        raise SystemExit(1)

    resolved_database = database_path or get_settings().database_path
    resolved_snapshot = snapshot_path or str(
        default_restore_snapshot_path(resolved_database, snapshot_dir=snapshot_dir)
    )

    try:
        result = restore_sqlite_database(backup_path, resolved_database, snapshot_path=resolved_snapshot)
    except FileNotFoundError as exc:
        click.echo(str(exc), err=True)
        raise SystemExit(1) from exc
    except Exception as exc:
        click.echo(f"Restore failed: {exc}", err=True)
        raise SystemExit(1) from exc

    click.echo(f"Backup:      {result.backup_path}")
    click.echo(f"Restored to: {result.target_path}")
    if result.snapshot_path:
        click.echo(f"Snapshot:    {result.snapshot_path}")
    click.echo(f"Size bytes:  {result.size_bytes}")
    click.echo("Restart the coordinator and run the manager smoke check before resuming traffic.")


@main.command()
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
def status(config_path: str):
    """Show worker status."""
    from hive_compute.worker.config_model import load_worker_config
    from hive_compute.worker.keychain import describe_worker_token_storage

    config = load_worker_config(config_path)
    raw = config.model_dump()

    click.echo(f"Worker ID:      {config.worker_id or 'unknown'}")
    click.echo(f"Coordinator:    {config.coordinator_url or 'unknown'}")
    click.echo(f"Token storage:  {describe_worker_token_storage(raw)}")
    click.echo(f"Handlers:       {', '.join(config.handlers.keys())}")
    click.echo(f"Idle minutes:   {config.idle_detection.min_idle_minutes}")
    click.echo(f"Poll interval:  {config.polling.interval_seconds}s")


@main.command()
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
@click.option("--skip-network", is_flag=True, help="Skip coordinator reachability checks")
@click.option(
    "--smoke-backend",
    is_flag=True,
    help="Run a tiny local transcription through the configured backend",
)
def doctor(config_path: str, skip_network: bool, smoke_backend: bool):
    """Run local diagnostics for a worker install."""
    from hive_compute.worker.diagnostics import run_doctor

    checks = run_doctor(config_path, check_network=not skip_network, run_backend_smoke=smoke_backend)
    failures = 0
    warnings = 0
    labels = {"ok": "OK", "warn": "WARN", "fail": "FAIL"}

    for check in checks:
        if check.status == "fail":
            failures += 1
        elif check.status == "warn":
            warnings += 1

        click.echo(f"[{labels[check.status]}] {check.name:<13} {check.summary}")
        if check.detail:
            click.echo(f"       {check.detail}")

    if failures:
        click.echo(f"Doctor found {failures} failure(s) and {warnings} warning(s).", err=True)
        raise SystemExit(1)

    if warnings:
        click.echo(f"Doctor found {warnings} warning(s).")
    else:
        click.echo("Doctor found no issues.")


@main.command()
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
@click.option("--worker-id", default=None, help="Override worker ID if the config file is missing")
@click.option("--lines", default=80, type=click.IntRange(1, None), help="Tail the last N log lines per file")
def logs(config_path: str, worker_id: str | None, lines: int):
    """Print the current worker's LaunchAgent logs."""
    from hive_compute.worker.diagnostics import read_log_tail, resolve_worker_log_paths

    if worker_id is None:
        worker_id = _extract_worker_id(config_path)

    if not worker_id:
        click.echo("Worker ID is required to inspect logs.", err=True)
        raise SystemExit(1)

    found_logs = False
    for log_type, path in resolve_worker_log_paths(worker_id).items():
        click.echo(f"==> {path} ({log_type}) <==")
        if not path.exists():
            click.echo("(missing)")
            click.echo()
            continue

        found_logs = True
        tail_lines = read_log_tail(path, lines=lines)
        if tail_lines:
            for line in tail_lines:
                click.echo(line)
        else:
            click.echo("(empty)")
        click.echo()

    if not found_logs:
        click.echo("No worker log files found yet. Start the worker or install the LaunchAgent first.", err=True)
        raise SystemExit(1)


@main.command("install-service")
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
def install_service(config_path: str):
    """Install the worker as a per-user macOS LaunchAgent."""
    from hive_compute.worker.config_model import load_worker_config
    from hive_compute.worker.launchd import install_launch_agent

    config = load_worker_config(config_path)
    worker_id = config.worker_id
    if not worker_id:
        click.echo("Config is missing worker_id. Run 'hive init' again.", err=True)
        raise SystemExit(1)

    try:
        label, plist_path = install_launch_agent(worker_id=worker_id, config_path=config_path)
    except Exception as e:
        click.echo(f"Failed to install launchd service: {e}", err=True)
        raise SystemExit(1) from e

    click.echo(f"Installed LaunchAgent: {label}")
    click.echo(f"Plist path:           {plist_path}")
    click.echo("The worker will now launch at login and stay running in the background.")


@main.command("uninstall-service")
@click.option("--config", "config_path", default="~/.ambient/config.yaml", help="Config file path")
@click.option("--worker-id", default=None, help="Override worker ID if the config file is missing")
def uninstall_service(config_path: str, worker_id: str | None):
    """Remove the per-user macOS LaunchAgent for this worker."""
    from hive_compute.worker.launchd import uninstall_launch_agent

    if worker_id is None:
        worker_id = _extract_worker_id(config_path)

    if not worker_id:
        click.echo("Worker ID is required to remove the LaunchAgent.", err=True)
        raise SystemExit(1)

    try:
        label, plist_path = uninstall_launch_agent(worker_id)
    except Exception as e:
        click.echo(f"Failed to remove launchd service: {e}", err=True)
        raise SystemExit(1) from e

    click.echo(f"Removed LaunchAgent: {label}")
    click.echo(f"Plist path:         {plist_path}")


def _load_config_file(config_path: str) -> dict:
    """Load and validate worker config via Pydantic model, return as dict."""
    from hive_compute.worker.config_model import load_worker_config
    return load_worker_config(config_path).model_dump()


def _load_transcribe_handler(config_path: str):
    from hive_compute.handlers.whisper_handler import WhisperHandler

    config = _load_config_file(config_path)
    transcribe = config.get("handlers", {}).get("transcribe")
    if not transcribe:
        click.echo("Config is missing handlers.transcribe. Run 'hive init' again.", err=True)
        raise SystemExit(1)
    return WhisperHandler(transcribe)


def _write_config_file(config_path: str, config: dict) -> None:
    from pathlib import Path

    import yaml

    config_path_obj = Path(config_path).expanduser()
    config_path_obj.parent.mkdir(parents=True, exist_ok=True)
    with open(config_path_obj, "w") as f:
        yaml.safe_dump(config, f, sort_keys=False, default_flow_style=False)


def _sanitize_config_for_display(config: dict) -> dict:
    sanitized = dict(config)
    if sanitized.get("worker_token"):
        sanitized["worker_token"] = "<redacted>"
    return sanitized


def _extract_worker_id(config_path: str) -> str | None:
    """Extract just the worker_id from config without full validation.

    Used by logs/uninstall-service which only need worker_id and should
    work even when coordinator_url is missing or config is partial.
    """
    from pathlib import Path

    import yaml

    config_path_obj = Path(config_path).expanduser()
    if not config_path_obj.exists():
        click.echo("Config not found. Pass --worker-id directly.", err=True)
        raise SystemExit(1)

    try:
        with open(config_path_obj) as f:
            data = yaml.safe_load(f) or {}
    except Exception as exc:
        click.echo("Config could not be parsed. Pass --worker-id directly.", err=True)
        raise SystemExit(1) from exc

    if isinstance(data, dict):
        val = data.get("worker_id")
        if val is not None:
            return str(val) if not isinstance(val, str) else val
    return None
