from pathlib import Path
from typing import Optional, List
import json

import typer
import sys

from seedpass.core.manager import PasswordManager
from seedpass.core.entry_types import EntryType
from seedpass.core.api import (
    VaultService,
    ProfileService,
    SyncService,
    EntryService,
    ConfigService,
    UtilityService,
    NostrService,
    ChangePasswordRequest,
    UnlockRequest,
    BackupParentSeedRequest,
    ProfileSwitchRequest,
    ProfileRemoveRequest,
)
import uvicorn
from . import api as api_module

import importlib
import importlib.util
import subprocess

app = typer.Typer(
    help="SeedPass command line interface",
    invoke_without_command=True,
)

# Global option shared across all commands
fingerprint_option = typer.Option(
    None,
    "--fingerprint",
    "-f",
    help="Specify which seed profile to use",
)

# Sub command groups
entry_app = typer.Typer(help="Manage individual entries")
vault_app = typer.Typer(help="Manage the entire vault")
nostr_app = typer.Typer(help="Interact with Nostr relays")
config_app = typer.Typer(help="Get or set configuration values")
fingerprint_app = typer.Typer(help="Manage seed profiles")
util_app = typer.Typer(help="Utility commands")
api_app = typer.Typer(help="Run the API server")

app.add_typer(entry_app, name="entry")
app.add_typer(vault_app, name="vault")
app.add_typer(nostr_app, name="nostr")
app.add_typer(config_app, name="config")
app.add_typer(fingerprint_app, name="fingerprint")
app.add_typer(util_app, name="util")
app.add_typer(api_app, name="api")


def _get_pm(ctx: typer.Context) -> PasswordManager:
    """Return a PasswordManager optionally selecting a fingerprint."""
    fp = ctx.obj.get("fingerprint")
    if fp is None:
        pm = PasswordManager()
    else:
        pm = PasswordManager(fingerprint=fp)
    return pm


def _get_services(
    ctx: typer.Context,
) -> tuple[VaultService, ProfileService, SyncService]:
    """Return service layer instances for the current context."""

    pm = _get_pm(ctx)
    return VaultService(pm), ProfileService(pm), SyncService(pm)


def _get_entry_service(ctx: typer.Context) -> EntryService:
    pm = _get_pm(ctx)
    return EntryService(pm)


def _get_config_service(ctx: typer.Context) -> ConfigService:
    pm = _get_pm(ctx)
    return ConfigService(pm)


def _get_util_service(ctx: typer.Context) -> UtilityService:
    pm = _get_pm(ctx)
    return UtilityService(pm)


def _get_nostr_service(ctx: typer.Context) -> NostrService:
    pm = _get_pm(ctx)
    return NostrService(pm)


def _gui_backend_available() -> bool:
    """Return True if a platform-specific BeeWare backend is installed."""
    for pkg in ("toga_gtk", "toga_winforms", "toga_cocoa"):
        if importlib.util.find_spec(pkg) is not None:
            return True
    return False


@app.callback(invoke_without_command=True)
def main(ctx: typer.Context, fingerprint: Optional[str] = fingerprint_option) -> None:
    """SeedPass CLI entry point.

    When called without a subcommand this launches the interactive TUI.
    """
    ctx.obj = {"fingerprint": fingerprint}
    if ctx.invoked_subcommand is None:
        tui = importlib.import_module("main")
        raise typer.Exit(tui.main(fingerprint=fingerprint))


@entry_app.command("list")
def entry_list(
    ctx: typer.Context,
    sort: str = typer.Option(
        "index", "--sort", help="Sort by 'index', 'label', or 'updated'"
    ),
    kind: Optional[str] = typer.Option(None, "--kind", help="Filter by entry type"),
    archived: bool = typer.Option(False, "--archived", help="Include archived"),
) -> None:
    """List entries in the vault."""
    service = _get_entry_service(ctx)
    entries = service.list_entries(
        sort_by=sort, filter_kind=kind, include_archived=archived
    )
    for idx, label, username, url, is_archived in entries:
        line = f"{idx}: {label}"
        if username:
            line += f" ({username})"
        if url:
            line += f" {url}"
        if is_archived:
            line += " [archived]"
        typer.echo(line)


@entry_app.command("search")
def entry_search(
    ctx: typer.Context,
    query: str,
    kind: List[str] = typer.Option(
        None,
        "--kind",
        "-k",
        help="Filter by entry kinds (can be repeated)",
    ),
) -> None:
    """Search entries."""
    service = _get_entry_service(ctx)
    kinds = list(kind) if kind else None
    results = service.search_entries(query, kinds=kinds)
    if not results:
        typer.echo("No matching entries found")
        return
    for idx, label, username, url, _arch in results:
        line = f"{idx}: {label}"
        if username:
            line += f" ({username})"
        if url:
            line += f" {url}"
        typer.echo(line)


@entry_app.command("get")
def entry_get(ctx: typer.Context, query: str) -> None:
    """Retrieve a single entry's secret."""
    service = _get_entry_service(ctx)
    matches = service.search_entries(query)
    if len(matches) == 0:
        typer.echo("No matching entries found")
        raise typer.Exit(code=1)
    if len(matches) > 1:
        typer.echo("Matches:")
        for idx, label, username, _url, _arch in matches:
            name = f"{idx}: {label}"
            if username:
                name += f" ({username})"
            typer.echo(name)
        raise typer.Exit(code=1)

    index = matches[0][0]
    entry = service.retrieve_entry(index)
    etype = entry.get("type", entry.get("kind"))
    if etype == EntryType.PASSWORD.value:
        length = int(entry.get("length", 12))
        password = service.generate_password(length, index)
        typer.echo(password)
    elif etype == EntryType.TOTP.value:
        code = service.get_totp_code(index)
        typer.echo(code)
    else:
        typer.echo("Unsupported entry type")
        raise typer.Exit(code=1)


@entry_app.command("add")
def entry_add(
    ctx: typer.Context,
    label: str,
    length: int = typer.Option(12, "--length"),
    username: Optional[str] = typer.Option(None, "--username"),
    url: Optional[str] = typer.Option(None, "--url"),
    no_special: bool = typer.Option(
        False, "--no-special", help="Exclude special characters", is_flag=True
    ),
    allowed_special_chars: Optional[str] = typer.Option(
        None, "--allowed-special-chars", help="Explicit set of special characters"
    ),
    special_mode: Optional[str] = typer.Option(
        None,
        "--special-mode",
        help="Special character mode",
    ),
    exclude_ambiguous: bool = typer.Option(
        False,
        "--exclude-ambiguous",
        help="Exclude ambiguous characters",
        is_flag=True,
    ),
    min_uppercase: Optional[int] = typer.Option(None, "--min-uppercase"),
    min_lowercase: Optional[int] = typer.Option(None, "--min-lowercase"),
    min_digits: Optional[int] = typer.Option(None, "--min-digits"),
    min_special: Optional[int] = typer.Option(None, "--min-special"),
) -> None:
    """Add a new password entry and output its index."""
    service = _get_entry_service(ctx)
    kwargs = {}
    if no_special:
        kwargs["include_special_chars"] = False
    if allowed_special_chars is not None:
        kwargs["allowed_special_chars"] = allowed_special_chars
    if special_mode is not None:
        kwargs["special_mode"] = special_mode
    if exclude_ambiguous:
        kwargs["exclude_ambiguous"] = True
    if min_uppercase is not None:
        kwargs["min_uppercase"] = min_uppercase
    if min_lowercase is not None:
        kwargs["min_lowercase"] = min_lowercase
    if min_digits is not None:
        kwargs["min_digits"] = min_digits
    if min_special is not None:
        kwargs["min_special"] = min_special

    index = service.add_entry(label, length, username, url, **kwargs)
    typer.echo(str(index))


@entry_app.command("add-totp")
def entry_add_totp(
    ctx: typer.Context,
    label: str,
    index: Optional[int] = typer.Option(None, "--index", help="Derivation index"),
    secret: Optional[str] = typer.Option(None, "--secret", help="Import secret"),
    period: int = typer.Option(30, "--period", help="TOTP period in seconds"),
    digits: int = typer.Option(6, "--digits", help="Number of TOTP digits"),
) -> None:
    """Add a TOTP entry and output the otpauth URI."""
    service = _get_entry_service(ctx)
    uri = service.add_totp(
        label,
        index=index,
        secret=secret,
        period=period,
        digits=digits,
    )
    typer.echo(uri)


@entry_app.command("add-ssh")
def entry_add_ssh(
    ctx: typer.Context,
    label: str,
    index: Optional[int] = typer.Option(None, "--index", help="Derivation index"),
    notes: str = typer.Option("", "--notes", help="Entry notes"),
) -> None:
    """Add an SSH key entry and output its index."""
    service = _get_entry_service(ctx)
    idx = service.add_ssh_key(
        label,
        index=index,
        notes=notes,
    )
    typer.echo(str(idx))


@entry_app.command("add-pgp")
def entry_add_pgp(
    ctx: typer.Context,
    label: str,
    index: Optional[int] = typer.Option(None, "--index", help="Derivation index"),
    key_type: str = typer.Option("ed25519", "--key-type", help="Key type"),
    user_id: str = typer.Option("", "--user-id", help="User ID"),
    notes: str = typer.Option("", "--notes", help="Entry notes"),
) -> None:
    """Add a PGP key entry and output its index."""
    service = _get_entry_service(ctx)
    idx = service.add_pgp_key(
        label,
        index=index,
        key_type=key_type,
        user_id=user_id,
        notes=notes,
    )
    typer.echo(str(idx))


@entry_app.command("add-nostr")
def entry_add_nostr(
    ctx: typer.Context,
    label: str,
    index: Optional[int] = typer.Option(None, "--index", help="Derivation index"),
    notes: str = typer.Option("", "--notes", help="Entry notes"),
) -> None:
    """Add a Nostr key entry and output its index."""
    service = _get_entry_service(ctx)
    idx = service.add_nostr_key(
        label,
        index=index,
        notes=notes,
    )
    typer.echo(str(idx))


@entry_app.command("add-seed")
def entry_add_seed(
    ctx: typer.Context,
    label: str,
    index: Optional[int] = typer.Option(None, "--index", help="Derivation index"),
    words: int = typer.Option(24, "--words", help="Word count"),
    notes: str = typer.Option("", "--notes", help="Entry notes"),
) -> None:
    """Add a derived seed phrase entry and output its index."""
    service = _get_entry_service(ctx)
    idx = service.add_seed(
        label,
        index=index,
        words=words,
        notes=notes,
    )
    typer.echo(str(idx))


@entry_app.command("add-key-value")
def entry_add_key_value(
    ctx: typer.Context,
    label: str,
    key: str = typer.Option(..., "--key", help="Key name"),
    value: str = typer.Option(..., "--value", help="Stored value"),
    notes: str = typer.Option("", "--notes", help="Entry notes"),
) -> None:
    """Add a key/value entry and output its index."""
    service = _get_entry_service(ctx)
    idx = service.add_key_value(label, key, value, notes=notes)
    typer.echo(str(idx))


@entry_app.command("add-managed-account")
def entry_add_managed_account(
    ctx: typer.Context,
    label: str,
    index: Optional[int] = typer.Option(None, "--index", help="Derivation index"),
    notes: str = typer.Option("", "--notes", help="Entry notes"),
) -> None:
    """Add a managed account seed entry and output its index."""
    service = _get_entry_service(ctx)
    idx = service.add_managed_account(
        label,
        index=index,
        notes=notes,
    )
    typer.echo(str(idx))


@entry_app.command("modify")
def entry_modify(
    ctx: typer.Context,
    entry_id: int,
    label: Optional[str] = typer.Option(None, "--label"),
    username: Optional[str] = typer.Option(None, "--username"),
    url: Optional[str] = typer.Option(None, "--url"),
    notes: Optional[str] = typer.Option(None, "--notes"),
    period: Optional[int] = typer.Option(
        None, "--period", help="TOTP period in seconds"
    ),
    digits: Optional[int] = typer.Option(None, "--digits", help="TOTP digits"),
    key: Optional[str] = typer.Option(None, "--key", help="New key"),
    value: Optional[str] = typer.Option(None, "--value", help="New value"),
) -> None:
    """Modify an existing entry."""
    service = _get_entry_service(ctx)
    try:
        service.modify_entry(
            entry_id,
            username=username,
            url=url,
            notes=notes,
            label=label,
            period=period,
            digits=digits,
            key=key,
            value=value,
        )
    except ValueError as e:
        typer.echo(str(e))
        sys.stdout.flush()
        raise typer.Exit(code=1)


@entry_app.command("archive")
def entry_archive(ctx: typer.Context, entry_id: int) -> None:
    """Archive an entry."""
    service = _get_entry_service(ctx)
    service.archive_entry(entry_id)
    typer.echo(str(entry_id))


@entry_app.command("unarchive")
def entry_unarchive(ctx: typer.Context, entry_id: int) -> None:
    """Restore an archived entry."""
    service = _get_entry_service(ctx)
    service.restore_entry(entry_id)
    typer.echo(str(entry_id))


@entry_app.command("totp-codes")
def entry_totp_codes(ctx: typer.Context) -> None:
    """Display all current TOTP codes."""
    service = _get_entry_service(ctx)
    service.display_totp_codes()


@entry_app.command("export-totp")
def entry_export_totp(
    ctx: typer.Context, file: str = typer.Option(..., help="Output file")
) -> None:
    """Export all TOTP secrets to a JSON file."""
    service = _get_entry_service(ctx)
    data = service.export_totp_entries()
    Path(file).write_text(json.dumps(data, indent=2))
    typer.echo(str(file))


@vault_app.command("export")
def vault_export(
    ctx: typer.Context, file: str = typer.Option(..., help="Output file")
) -> None:
    """Export the vault profile to an encrypted file."""
    vault_service, _profile, _sync = _get_services(ctx)
    data = vault_service.export_profile()
    Path(file).write_bytes(data)
    typer.echo(str(file))


@vault_app.command("import")
def vault_import(
    ctx: typer.Context, file: str = typer.Option(..., help="Input file")
) -> None:
    """Import a vault profile from an encrypted file."""
    vault_service, _profile, _sync = _get_services(ctx)
    data = Path(file).read_bytes()
    vault_service.import_profile(data)
    typer.echo(str(file))


@vault_app.command("change-password")
def vault_change_password(ctx: typer.Context) -> None:
    """Change the master password used for encryption."""
    vault_service, _profile, _sync = _get_services(ctx)
    old_pw = typer.prompt("Current password", hide_input=True)
    new_pw = typer.prompt("New password", hide_input=True, confirmation_prompt=True)
    try:
        vault_service.change_password(
            ChangePasswordRequest(old_password=old_pw, new_password=new_pw)
        )
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error: {exc}")
        raise typer.Exit(code=1)
    typer.echo("Password updated")


@vault_app.command("unlock")
def vault_unlock(ctx: typer.Context) -> None:
    """Unlock the vault for the active profile."""
    vault_service, _profile, _sync = _get_services(ctx)
    password = typer.prompt("Master password", hide_input=True)
    try:
        resp = vault_service.unlock(UnlockRequest(password=password))
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error: {exc}")
        raise typer.Exit(code=1)
    typer.echo(f"Unlocked in {resp.duration:.2f}s")


@vault_app.command("lock")
def vault_lock(ctx: typer.Context) -> None:
    """Lock the vault and clear sensitive data from memory."""
    vault_service, _profile, _sync = _get_services(ctx)
    vault_service.lock()
    typer.echo("locked")


@app.command("lock")
def root_lock(ctx: typer.Context) -> None:
    """Lock the vault for the active profile."""
    vault_service, _profile, _sync = _get_services(ctx)
    vault_service.lock()
    typer.echo("locked")


@vault_app.command("stats")
def vault_stats(ctx: typer.Context) -> None:
    """Display statistics about the current seed profile."""
    vault_service, _profile, _sync = _get_services(ctx)
    stats = vault_service.stats()
    typer.echo(json.dumps(stats, indent=2))


@vault_app.command("reveal-parent-seed")
def vault_reveal_parent_seed(
    ctx: typer.Context,
    file: Optional[str] = typer.Option(
        None, "--file", help="Save encrypted seed to this path"
    ),
) -> None:
    """Display the parent seed and optionally write an encrypted backup file."""
    vault_service, _profile, _sync = _get_services(ctx)
    password = typer.prompt("Master password", hide_input=True)
    vault_service.backup_parent_seed(
        BackupParentSeedRequest(path=Path(file) if file else None, password=password)
    )


@nostr_app.command("sync")
def nostr_sync(ctx: typer.Context) -> None:
    """Sync with configured Nostr relays."""
    _vault, _profile, sync_service = _get_services(ctx)
    model = sync_service.sync()
    if model:
        typer.echo("Event IDs:")
        typer.echo(f"- manifest: {model.manifest_id}")
        for cid in model.chunk_ids:
            typer.echo(f"- chunk: {cid}")
        for did in model.delta_ids:
            typer.echo(f"- delta: {did}")
    else:
        typer.echo("Error: Failed to sync vault")


@nostr_app.command("get-pubkey")
def nostr_get_pubkey(ctx: typer.Context) -> None:
    """Display the active profile's npub."""
    service = _get_nostr_service(ctx)
    npub = service.get_pubkey()
    typer.echo(npub)


@nostr_app.command("list-relays")
def nostr_list_relays(ctx: typer.Context) -> None:
    """Display configured Nostr relays."""
    service = _get_nostr_service(ctx)
    relays = service.list_relays()
    for i, r in enumerate(relays, 1):
        typer.echo(f"{i}: {r}")


@nostr_app.command("add-relay")
def nostr_add_relay(ctx: typer.Context, url: str) -> None:
    """Add a relay URL."""
    service = _get_nostr_service(ctx)
    try:
        service.add_relay(url)
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error: {exc}")
        raise typer.Exit(code=1)
    typer.echo("Added")


@nostr_app.command("remove-relay")
def nostr_remove_relay(ctx: typer.Context, idx: int) -> None:
    """Remove a relay by index (1-based)."""
    service = _get_nostr_service(ctx)
    try:
        service.remove_relay(idx)
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error: {exc}")
        raise typer.Exit(code=1)
    typer.echo("Removed")


@config_app.command("get")
def config_get(ctx: typer.Context, key: str) -> None:
    """Get a configuration value."""
    service = _get_config_service(ctx)
    value = service.get(key)
    if value is None:
        typer.echo("Key not found")
    else:
        typer.echo(str(value))


@config_app.command("set")
def config_set(ctx: typer.Context, key: str, value: str) -> None:
    """Set a configuration value."""
    service = _get_config_service(ctx)

    try:
        val = (
            [r.strip() for r in value.split(",") if r.strip()]
            if key == "relays"
            else value
        )
        service.set(key, val)
    except KeyError:
        typer.echo("Unknown key")
        raise typer.Exit(code=1)
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error: {exc}")
        raise typer.Exit(code=1)

    typer.echo("Updated")


@config_app.command("toggle-secret-mode")
def config_toggle_secret_mode(ctx: typer.Context) -> None:
    """Interactively enable or disable secret mode.

    When enabled, newly generated and retrieved passwords are copied to the
    clipboard instead of printed to the screen.
    """
    service = _get_config_service(ctx)
    try:
        enabled = service.get_secret_mode_enabled()
        delay = service.get_clipboard_clear_delay()
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error loading settings: {exc}")
        raise typer.Exit(code=1)

    typer.echo(f"Secret mode is currently {'ON' if enabled else 'OFF'}")
    choice = (
        typer.prompt(
            "Enable secret mode? (y/n, blank to keep)", default="", show_default=False
        )
        .strip()
        .lower()
    )
    if choice in ("y", "yes"):
        enabled = True
    elif choice in ("n", "no"):
        enabled = False

    inp = typer.prompt(
        f"Clipboard clear delay in seconds [{delay}]", default="", show_default=False
    ).strip()
    if inp:
        try:
            delay = int(inp)
            if delay <= 0:
                typer.echo("Delay must be positive")
                raise typer.Exit(code=1)
        except ValueError:
            typer.echo("Invalid number")
            raise typer.Exit(code=1)

    try:
        service.set_secret_mode(enabled, delay)
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error: {exc}")
        raise typer.Exit(code=1)

    status = "enabled" if enabled else "disabled"
    typer.echo(f"Secret mode {status}.")


@config_app.command("toggle-offline")
def config_toggle_offline(ctx: typer.Context) -> None:
    """Enable or disable offline mode."""
    service = _get_config_service(ctx)
    try:
        enabled = service.get_offline_mode()
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error loading settings: {exc}")
        raise typer.Exit(code=1)

    typer.echo(f"Offline mode is currently {'ON' if enabled else 'OFF'}")
    choice = (
        typer.prompt(
            "Enable offline mode? (y/n, blank to keep)", default="", show_default=False
        )
        .strip()
        .lower()
    )
    if choice in ("y", "yes"):
        enabled = True
    elif choice in ("n", "no"):
        enabled = False

    try:
        service.set_offline_mode(enabled)
    except Exception as exc:  # pragma: no cover - pass through errors
        typer.echo(f"Error: {exc}")
        raise typer.Exit(code=1)

    status = "enabled" if enabled else "disabled"
    typer.echo(f"Offline mode {status}.")


@fingerprint_app.command("list")
def fingerprint_list(ctx: typer.Context) -> None:
    """List available seed profiles."""
    _vault, profile_service, _sync = _get_services(ctx)
    for fp in profile_service.list_profiles():
        typer.echo(fp)


@fingerprint_app.command("add")
def fingerprint_add(ctx: typer.Context) -> None:
    """Create a new seed profile."""
    _vault, profile_service, _sync = _get_services(ctx)
    profile_service.add_profile()


@fingerprint_app.command("remove")
def fingerprint_remove(ctx: typer.Context, fingerprint: str) -> None:
    """Remove a seed profile."""
    _vault, profile_service, _sync = _get_services(ctx)
    profile_service.remove_profile(ProfileRemoveRequest(fingerprint=fingerprint))


@fingerprint_app.command("switch")
def fingerprint_switch(ctx: typer.Context, fingerprint: str) -> None:
    """Switch to another seed profile."""
    _vault, profile_service, _sync = _get_services(ctx)
    password = typer.prompt("Master password", hide_input=True)
    profile_service.switch_profile(
        ProfileSwitchRequest(fingerprint=fingerprint, password=password)
    )


@util_app.command("generate-password")
def generate_password(
    ctx: typer.Context,
    length: int = 24,
    no_special: bool = typer.Option(
        False, "--no-special", help="Exclude special characters", is_flag=True
    ),
    allowed_special_chars: Optional[str] = typer.Option(
        None, "--allowed-special-chars", help="Explicit set of special characters"
    ),
    special_mode: Optional[str] = typer.Option(
        None,
        "--special-mode",
        help="Special character mode",
    ),
    exclude_ambiguous: bool = typer.Option(
        False,
        "--exclude-ambiguous",
        help="Exclude ambiguous characters",
        is_flag=True,
    ),
    min_uppercase: Optional[int] = typer.Option(None, "--min-uppercase"),
    min_lowercase: Optional[int] = typer.Option(None, "--min-lowercase"),
    min_digits: Optional[int] = typer.Option(None, "--min-digits"),
    min_special: Optional[int] = typer.Option(None, "--min-special"),
) -> None:
    """Generate a strong password."""
    service = _get_util_service(ctx)
    kwargs = {}
    if no_special:
        kwargs["include_special_chars"] = False
    if allowed_special_chars is not None:
        kwargs["allowed_special_chars"] = allowed_special_chars
    if special_mode is not None:
        kwargs["special_mode"] = special_mode
    if exclude_ambiguous:
        kwargs["exclude_ambiguous"] = True
    if min_uppercase is not None:
        kwargs["min_uppercase"] = min_uppercase
    if min_lowercase is not None:
        kwargs["min_lowercase"] = min_lowercase
    if min_digits is not None:
        kwargs["min_digits"] = min_digits
    if min_special is not None:
        kwargs["min_special"] = min_special

    password = service.generate_password(length, **kwargs)
    typer.echo(password)


@util_app.command("verify-checksum")
def verify_checksum(ctx: typer.Context) -> None:
    """Verify the SeedPass script checksum."""
    service = _get_util_service(ctx)
    service.verify_checksum()


@util_app.command("update-checksum")
def update_checksum(ctx: typer.Context) -> None:
    """Regenerate the script checksum file."""
    service = _get_util_service(ctx)
    service.update_checksum()


@api_app.command("start")
def api_start(ctx: typer.Context, host: str = "127.0.0.1", port: int = 8000) -> None:
    """Start the SeedPass API server."""
    token = api_module.start_server(ctx.obj.get("fingerprint"))
    typer.echo(f"API token: {token}")
    uvicorn.run(api_module.app, host=host, port=port)


@api_app.command("stop")
def api_stop(ctx: typer.Context, host: str = "127.0.0.1", port: int = 8000) -> None:
    """Stop the SeedPass API server."""
    import requests

    try:
        requests.post(
            f"http://{host}:{port}/api/v1/shutdown",
            headers={"Authorization": f"Bearer {api_module._token}"},
            timeout=2,
        )
    except Exception as exc:  # pragma: no cover - best effort
        typer.echo(f"Failed to stop server: {exc}")


@app.command()
def gui() -> None:
    """Launch the BeeWare GUI.

    If the platform specific backend is missing, attempt to install it and
    retry launching the GUI.
    """
    if not _gui_backend_available():
        if sys.platform.startswith("linux"):
            pkg = "toga-gtk"
        elif sys.platform == "win32":
            pkg = "toga-winforms"
        elif sys.platform == "darwin":
            pkg = "toga-cocoa"
        else:
            typer.echo(
                f"Unsupported platform '{sys.platform}' for BeeWare GUI.",
                err=True,
            )
            raise typer.Exit(1)

        typer.echo(f"Attempting to install {pkg} for GUI support...")
        try:
            subprocess.check_call([sys.executable, "-m", "pip", "install", pkg])
            typer.echo(f"Successfully installed {pkg}.")
        except subprocess.CalledProcessError as exc:
            typer.echo(f"Failed to install {pkg}: {exc}", err=True)
            raise typer.Exit(1)

    if not _gui_backend_available():
        typer.echo(
            "BeeWare GUI backend still unavailable after installation attempt.",
            err=True,
        )
        raise typer.Exit(1)

    from seedpass_gui.app import main

    main()


if __name__ == "__main__":
    app()
