"""CLI: serve, setup, check subcommands (click-based)."""

from __future__ import annotations

import asyncio
import json as json_mod
import logging
import os
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from typing import Any
from urllib.request import Request, urlopen

import click
import yaml

from synology_mcp import __version__

_LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"

_CONFIG_DIR = Path.home() / ".config" / "synology-mcp"

_PYPI_PACKAGE = "synology-mcp"
_VERSION_CHECK_INTERVAL = 86400  # 24 hours


# --- Version checking and upgrades ---


def _get_current_version() -> str:
    """Return the running version."""
    try:
        from importlib.metadata import version as _pkg_version

        return _pkg_version(_PYPI_PACKAGE)
    except Exception:  # noqa: BLE001
        return __version__


def _get_latest_pypi_version() -> str | None:
    """Fetch the latest version from PyPI. Returns None on any failure."""
    try:
        req = Request(
            f"https://pypi.org/pypi/{_PYPI_PACKAGE}/json",
            headers={"User-Agent": f"{_PYPI_PACKAGE}/{_get_current_version()}"},
        )
        with urlopen(req, timeout=5) as resp:  # noqa: S310
            data = json_mod.loads(resp.read())
        result: str = data["info"]["version"]
        return result
    except Exception:  # noqa: BLE001
        return None


def _version_tuple(v: str) -> tuple[int, ...]:
    try:
        return tuple(int(x) for x in v.split("."))
    except Exception:  # noqa: BLE001
        return (0,)


def _detect_installer() -> str | None:
    """Detect whether the tool was installed via uv tool or pipx."""
    import shutil

    bin_path = shutil.which(_PYPI_PACKAGE) or ""
    if "uv" in bin_path and "tools" in bin_path:
        return "uv"
    if "pipx" in bin_path:
        return "pipx"
    return None


def _load_global_state() -> dict[str, Any]:
    """Load global (non-instance) state for version tracking."""
    path = Path.home() / ".local" / "state" / "synology-mcp" / "global.yaml"
    if not path.exists():
        return {}
    try:
        raw: dict[str, Any] = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
        return raw
    except Exception:  # noqa: BLE001
        return {}


def _save_global_state(state: dict[str, Any]) -> None:
    """Save global state."""
    path = Path.home() / ".local" / "state" / "synology-mcp" / "global.yaml"
    path.parent.mkdir(parents=True, exist_ok=True)
    raw_text = yaml.dump(state, default_flow_style=False, sort_keys=False)
    path.write_text("# Auto-generated by synology-mcp.\n" + raw_text, encoding="utf-8")


def _check_for_update(state: dict[str, Any], *, force: bool = False) -> str | None:
    """Check PyPI for a newer version. Returns latest version string if newer, else None."""
    now = datetime.now().timestamp()
    if not force:
        last = state.get("last_version_check")
        if last:
            try:
                elapsed = now - datetime.fromisoformat(last).timestamp()
                if elapsed < _VERSION_CHECK_INTERVAL:
                    latest: str | None = state.get("latest_known_version")
                    current = _get_current_version()
                    if latest and _version_tuple(latest) > _version_tuple(current):
                        return latest
                    return None
            except Exception:  # noqa: BLE001
                pass

    latest = _get_latest_pypi_version()
    state["last_version_check"] = datetime.now().isoformat()
    if not latest:
        return None

    state["latest_known_version"] = latest
    current = _get_current_version()

    if _version_tuple(latest) > _version_tuple(current):
        return latest
    return None


def _do_auto_upgrade(state: dict[str, Any]) -> bool:
    """Upgrade to the latest version. Returns True on success."""
    installer = _detect_installer()
    current = _get_current_version()

    if installer == "uv":
        cmd = ["uv", "tool", "install", f"{_PYPI_PACKAGE}@latest"]
    elif installer == "pipx":
        cmd = ["pipx", "upgrade", _PYPI_PACKAGE]
    else:
        click.echo(
            "Cannot detect package manager (uv or pipx). "
            f"Upgrade manually: uv tool upgrade {_PYPI_PACKAGE}"
        )
        return False

    click.echo(f"Upgrading {_PYPI_PACKAGE} via {installer}...")
    result = subprocess.run(cmd, capture_output=True, text=True)  # noqa: S603
    if result.returncode == 0:
        state["previous_version"] = current
        _save_global_state(state)
        click.echo(click.style("Upgrade complete — takes effect on next run.", fg="green"))
        return True
    click.echo(click.style(f"Upgrade failed: {result.stderr.strip()}", fg="red"), err=True)
    return False


def _do_revert(target_version: str | None = None) -> None:
    """Revert to a specific version or the previously recorded one."""
    state = _load_global_state()
    current = _get_current_version()

    prev: str | None
    if target_version:
        prev = target_version
    else:
        prev = state.get("previous_version")
        if not prev:
            click.echo("No previous version recorded — nothing to revert to.")
            click.echo(f"Tip: specify a version explicitly: {_PYPI_PACKAGE} --revert 0.1.0")
            return

    if prev == current:
        click.echo(f"Already running {current} — nothing to do.")
        return

    installer = _detect_installer()

    if installer == "uv":
        cmd = ["uv", "tool", "install", "--force", f"{_PYPI_PACKAGE}=={prev}"]
    elif installer == "pipx":
        cmd = ["pipx", "install", "--force", f"{_PYPI_PACKAGE}=={prev}"]
    else:
        click.echo(
            f"Cannot detect package manager. Install manually:\n"
            f"  uv tool install --force {_PYPI_PACKAGE}=={prev}\n"
            f"  pipx install --force {_PYPI_PACKAGE}=={prev}"
        )
        return

    click.echo(f"Reverting from {current} to {prev}...")
    result = subprocess.run(cmd, capture_output=True, text=True)  # noqa: S603
    if result.returncode == 0:
        state["previous_version"] = None
        state["auto_upgrade"] = False
        _save_global_state(state)
        click.echo(click.style(f"Reverted to {prev}. Takes effect on next run.", fg="green"))
        click.echo("Auto-upgrade has been disabled to prevent immediately re-upgrading.")
        click.echo(f"Re-enable with: {_PYPI_PACKAGE} --auto-upgrade enable")
    else:
        click.echo(click.style(f"Revert failed: {result.stderr.strip()}", fg="red"), err=True)


def _init_early_logging(*, verbose: bool = False) -> None:
    """Configure logging early so config discovery/loading is visible.

    Checks --verbose flag first, then SYNOLOGY_LOG_LEVEL env var.
    Falls back to INFO. Will be reconfigured after config is loaded
    if the config specifies a different level.
    """
    if verbose:
        level = logging.DEBUG
    else:
        env_level = os.environ.get("SYNOLOGY_LOG_LEVEL", "info").upper()
        level = getattr(logging, env_level, logging.INFO)
    logging.basicConfig(level=level, format=_LOG_FORMAT, stream=sys.stderr)


def _configure_logging(level: str, log_file: str | None = None) -> None:
    """Reconfigure logging from loaded config values."""
    root = logging.getLogger()
    root.setLevel(getattr(logging, level.upper(), logging.INFO))

    if log_file:
        file_handler = logging.FileHandler(os.path.expanduser(log_file), encoding="utf-8")
        file_handler.setFormatter(logging.Formatter(_LOG_FORMAT))
        root.addHandler(file_handler)


@click.group(context_settings={"help_option_names": ["-h", "--help"]}, invoke_without_command=True)
@click.version_option(__version__, "-v", "--version", prog_name="synology-mcp")
@click.option("--check-update", is_flag=True, help="Check PyPI for a newer version")
@click.option(
    "--auto-upgrade",
    type=click.Choice(["enable", "disable"]),
    help="Enable or disable automatic upgrades",
)
@click.option(
    "--revert",
    nargs=1,
    required=False,
    default=None,
    metavar="[VERSION]",
    is_flag=False,
    flag_value="__PREVIOUS__",
    help="Revert to previous or specified version",
)
@click.pass_context
def main(
    ctx: click.Context,
    check_update: bool,
    auto_upgrade: str | None,
    revert: str | None,
) -> None:
    """synology-mcp — MCP server for Synology NAS."""
    if check_update:
        state = _load_global_state()
        latest = _check_for_update(state, force=True)
        _save_global_state(state)
        current = _get_current_version()
        if latest:
            click.echo(f"Update available: {current} -> {latest}")
            installer = _detect_installer()
            if installer == "uv":
                click.echo(f"Upgrade with: uv tool install {_PYPI_PACKAGE}@latest")
            elif installer == "pipx":
                click.echo(f"Upgrade with: pipx upgrade {_PYPI_PACKAGE}")
            else:
                click.echo(f"Upgrade with: uv tool install {_PYPI_PACKAGE}@latest")
        else:
            click.echo(f"You are running the latest version ({current}).")
        ctx.exit()

    if auto_upgrade is not None:
        state = _load_global_state()
        state["auto_upgrade"] = auto_upgrade == "enable"
        _save_global_state(state)
        status = "enabled" if state["auto_upgrade"] else "disabled"
        click.echo(f"Auto-upgrade {status}.")
        ctx.exit()

    if revert is not None:
        _do_revert(None if revert == "__PREVIOUS__" else revert)
        ctx.exit()

    # Track version changes for --revert
    state = _load_global_state()
    current = _get_current_version()
    last_known = state.get("running_version")
    if last_known and last_known != current:
        state["previous_version"] = last_known
    state["running_version"] = current
    _save_global_state(state)

    # Auto-upgrade check — only on interactive commands, not serve
    # (serve is launched by Claude Desktop; upgrading mid-launch is risky)
    if state.get("auto_upgrade") and ctx.invoked_subcommand != "serve":
        latest = _check_for_update(state)
        _save_global_state(state)
        if latest:
            _do_auto_upgrade(state)

    if ctx.invoked_subcommand is None:
        click.echo(ctx.get_help())


@main.command()
@click.option("-c", "--config", type=click.Path(), help="Path to config file")
def serve(config: str | None) -> None:
    """Start the MCP server (launched by Claude Desktop)."""
    _init_early_logging()

    from synology_mcp.core.config import load_config
    from synology_mcp.server import create_server

    try:
        app_config = load_config(config)
    except (FileNotFoundError, ValueError) as e:
        click.echo(click.style(f"Error: {e}", fg="red"), err=True)
        sys.exit(1)

    _configure_logging(app_config.logging.level, app_config.logging.file)

    server = create_server(app_config)
    server.run(transport="stdio")


@main.command()
@click.option("-c", "--config", type=click.Path(), help="Path to config file")
@click.option("-l", "--list", "list_configs", is_flag=True, help="List existing configurations")
@click.option("--verbose", is_flag=True, help="Enable debug logging")
def setup(config: str | None, list_configs: bool, verbose: bool) -> None:
    """Interactive credential setup and 2FA bootstrap."""
    _init_early_logging(verbose=verbose)

    if list_configs:
        _list_configurations()
        return

    # If --config is given, use the existing config-file flow
    if config:
        _setup_with_config(config, verbose)
        return

    # Try to load an existing config via discovery
    from synology_mcp.core.config import load_config

    try:
        app_config = load_config(None)
    except FileNotFoundError:
        # No config file found — enter interactive creation mode
        _setup_interactive(verbose)
        return
    except ValueError as e:
        click.echo(click.style(f"Error: {e}", fg="red"), err=True)
        sys.exit(1)

    # Config file exists — do credential setup against it
    _setup_credential_flow(app_config, verbose)


def _list_configurations() -> None:
    """List existing configuration files in the config directory."""
    if not _CONFIG_DIR.exists():
        click.echo("No configurations found.")
        click.echo(f"Config directory: {_CONFIG_DIR}")
        return

    yaml_files = sorted(_CONFIG_DIR.glob("*.yaml"))
    if not yaml_files:
        click.echo("No configurations found.")
        click.echo(f"Config directory: {_CONFIG_DIR}")
        return

    click.echo(f"Configurations in {_CONFIG_DIR}:\n")
    for f in yaml_files:
        try:
            raw = yaml.safe_load(f.read_text(encoding="utf-8")) or {}
            host = raw.get("connection", {}).get("host", "?")
            alias = raw.get("alias", "")
            instance_id = raw.get("instance_id", f.stem)
            label = alias or instance_id
            click.echo(f"  {f.name}")
            click.echo(f"    Name: {label}")
            click.echo(f"    Host: {host}")
            click.echo()
        except Exception:  # noqa: BLE001
            click.echo(f"  {f.name}  (could not parse)")


def _setup_interactive(verbose: bool) -> None:
    """Interactive setup when no config file exists."""
    click.echo("No configuration file found. Let's create one.\n")

    # Prompt for connection details
    host = click.prompt("NAS host (IP or hostname)")
    use_https = click.confirm("Use HTTPS?", default=False)
    permission = click.prompt(
        "File Station permission",
        type=click.Choice(["read", "write"]),
        default="read",
    )

    # Derive instance_id for file naming
    from synology_mcp.core.config import _derive_instance_id

    instance_id = _derive_instance_id(host)

    # Prompt for alias
    alias_input = click.prompt("Alias (friendly name, optional)", default="", show_default=False)
    alias: str | None = alias_input.strip() or None

    # Build config dict
    conn: dict[str, Any] = {"host": host}
    if use_https:
        conn["https"] = True
        conn["verify_ssl"] = click.confirm("Verify SSL certificate?", default=False)

    config_dict: dict[str, Any] = {
        "schema_version": 1,
        "instance_id": instance_id,
        "connection": conn,
        "modules": {
            "filestation": {
                "enabled": True,
                "permission": permission,
            }
        },
    }
    if alias:
        config_dict["alias"] = alias

    # Validate before writing
    from synology_mcp.core.config import AppConfig

    try:
        app_config = AppConfig(**config_dict)
    except ValueError as e:
        click.echo(click.style(f"Config validation failed: {e}", fg="red"), err=True)
        sys.exit(1)

    # Prompt for credentials
    click.echo()
    username = click.prompt("DSM username")
    password = click.prompt("DSM password", hide_input=True)

    # Store in keyring
    service = f"synology-mcp/{instance_id}"
    keyring_ok = _store_keyring(service, username, password)
    if not keyring_ok:
        return

    # Connect, validate login, fetch hostname
    click.echo("\nConnecting to NAS...")
    result = asyncio.run(_connect_and_login(app_config, username, password, service, verbose))
    if not result["success"]:
        return

    # Suggest hostname as alias if we fetched one and user didn't set an alias
    nas_hostname = result.get("hostname")
    if nas_hostname and not alias:
        use_hostname = click.confirm(
            f'NAS reports hostname "{nas_hostname}". Use as alias?', default=True
        )
        if use_hostname:
            alias = nas_hostname
            config_dict["alias"] = alias
            # Re-validate
            app_config = AppConfig(**config_dict)

    # Check for existing file before writing
    config_path = _CONFIG_DIR / f"{instance_id}.yaml"
    if config_path.exists() and not click.confirm(
        f"\n{config_path} already exists. Overwrite?", default=False
    ):
        click.echo("Aborted.")
        return

    # Write config file
    _CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    raw_yaml = yaml.dump(config_dict, default_flow_style=False, sort_keys=False)
    header = "# Generated by synology-mcp setup\n"
    config_path.write_text(header + raw_yaml, encoding="utf-8")
    click.echo(f"\nConfig written to {config_path}")

    # Emit Claude Desktop JSON snippet
    _emit_claude_desktop_snippet(app_config, config_path)


def _setup_with_config(config_path_str: str, verbose: bool) -> None:
    """Setup using an explicit config file path."""
    from synology_mcp.core.config import load_config

    try:
        app_config = load_config(config_path_str)
    except (FileNotFoundError, ValueError) as e:
        click.echo(click.style(f"Error: {e}", fg="red"), err=True)
        sys.exit(1)

    _setup_credential_flow(app_config, verbose)


def _setup_credential_flow(app_config: Any, verbose: bool) -> None:
    """Credential setup flow for an existing config."""
    from synology_mcp.core.config import AppConfig

    assert isinstance(app_config, AppConfig)

    if not verbose:
        _configure_logging(app_config.logging.level, app_config.logging.file)

    assert app_config.connection is not None
    click.echo(f"Setting up credentials for {app_config.display_name}")
    click.echo(f"  Host: {app_config.connection.host}")
    click.echo(f"  Instance: {app_config.instance_id}")
    click.echo()

    username = click.prompt("DSM username")
    password = click.prompt("DSM password", hide_input=True)

    service = f"synology-mcp/{app_config.instance_id}"
    keyring_ok = _store_keyring(service, username, password)
    if not keyring_ok:
        return

    click.echo("\nAttempting login...")
    asyncio.run(_setup_login(app_config, username, password, service))


def _store_keyring(service: str, username: str, password: str) -> bool:
    """Store credentials in the OS keyring. Returns True on success."""
    try:
        import keyring

        keyring.set_password(service, "username", username)
        keyring.set_password(service, "password", password)
        click.echo("\nCredentials stored in OS keyring.")
        return True
    except Exception:  # noqa: BLE001
        click.echo(
            "\nKeyring not available. Set environment variables instead:\n"
            f"  export SYNOLOGY_USERNAME={username}\n"
            f"  export SYNOLOGY_PASSWORD=<your-password>"
        )
        return False


async def _connect_and_login(
    config: Any, username: str, password: str, service: str, verbose: bool
) -> dict[str, Any]:
    """Connect to NAS, validate login, fetch hostname. Returns result dict."""
    from synology_mcp.core.client import DsmClient
    from synology_mcp.core.config import AppConfig

    assert isinstance(config, AppConfig)
    assert config.connection is not None

    protocol = "https" if config.connection.https else "http"
    base_url = f"{protocol}://{config.connection.host}:{config.connection.port}"

    result: dict[str, Any] = {"success": False}

    async with DsmClient(
        base_url=base_url,
        verify_ssl=config.connection.verify_ssl,
        timeout=config.connection.timeout,
    ) as client:
        await client.query_api_info()

        # Attempt login
        try:
            data = await client.request(
                "SYNO.API.Auth",
                "login",
                version=6,
                params={
                    "account": username,
                    "passwd": password,
                    "format": "sid",
                },
            )
        except Exception as e:  # noqa: BLE001
            error_code = getattr(e, "code", None)
            if error_code == 403:
                # 2FA flow
                click.echo("2FA is required. Enter the OTP code from your authenticator app.")
                otp_code = click.prompt("OTP code")
                try:
                    data = await client.request(
                        "SYNO.API.Auth",
                        "login",
                        version=6,
                        params={
                            "account": username,
                            "passwd": password,
                            "otp_code": otp_code,
                            "enable_device_token": "yes",
                            "device_name": "SynologyMCP",
                            "format": "sid",
                        },
                    )
                    device_id = data.get("did", "")
                    if device_id:
                        import keyring

                        keyring.set_password(service, "device_id", device_id)
                        click.echo(click.style("2FA bootstrap complete!", fg="green"))
                        click.echo("Device token stored in keyring.")
                    else:
                        click.echo(click.style("Login successful!", fg="green"))
                except Exception as e2:  # noqa: BLE001
                    click.echo(click.style(f"Login failed: {e2}", fg="red"), err=True)
                    return result
            else:
                click.echo(click.style(f"Login failed: {e}", fg="red"), err=True)
                return result

        sid = data.get("sid")
        if sid:
            client.sid = sid
            click.echo(click.style("Login successful!", fg="green"))

            # Fetch NAS hostname
            dsm_info = await client.fetch_dsm_info()
            nas_hostname = dsm_info.get("hostname") or ""
            if nas_hostname:
                result["hostname"] = nas_hostname

            # Store DSM version in state
            dsm_version = dsm_info.get("version_string", "")
            if dsm_version:
                result["dsm_version"] = dsm_version

            # Logout
            await client.request("SYNO.API.Auth", "logout", version=6, params={})

        result["success"] = True

    return result


async def _setup_login(config: object, username: str, password: str, service: str) -> None:
    """Attempt login during setup, handle 2FA if needed."""
    from synology_mcp.core.config import AppConfig

    assert isinstance(config, AppConfig)
    assert config.connection is not None

    from synology_mcp.core.client import DsmClient

    protocol = "https" if config.connection.https else "http"
    base_url = f"{protocol}://{config.connection.host}:{config.connection.port}"

    async with DsmClient(
        base_url=base_url,
        verify_ssl=config.connection.verify_ssl,
        timeout=config.connection.timeout,
    ) as client:
        await client.query_api_info()

        # Attempt login
        try:
            data = await client.request(
                "SYNO.API.Auth",
                "login",
                version=6,
                params={
                    "account": username,
                    "passwd": password,
                    "format": "sid",
                },
            )
            sid = data.get("sid")
            if sid:
                click.echo(click.style("Login successful!", fg="green"))
                # Logout
                client.sid = sid
                await client.request("SYNO.API.Auth", "logout", version=6, params={})
        except Exception as e:  # noqa: BLE001
            error_code = getattr(e, "code", None)
            if error_code == 403:
                click.echo("2FA is required. Enter the OTP code from your authenticator app.")
                otp_code = click.prompt("OTP code")

                try:
                    data = await client.request(
                        "SYNO.API.Auth",
                        "login",
                        version=6,
                        params={
                            "account": username,
                            "passwd": password,
                            "otp_code": otp_code,
                            "enable_device_token": "yes",
                            "device_name": "SynologyMCP",
                            "format": "sid",
                        },
                    )
                    device_id = data.get("did", "")
                    if device_id:
                        import keyring

                        keyring.set_password(service, "device_id", device_id)
                        click.echo(click.style("2FA bootstrap complete!", fg="green"))
                        click.echo("Device token stored in keyring.")
                    else:
                        click.echo(click.style("Login successful!", fg="green"))

                    sid = data.get("sid")
                    if sid:
                        client.sid = sid
                        await client.request("SYNO.API.Auth", "logout", version=6, params={})
                except Exception as e2:  # noqa: BLE001
                    click.echo(click.style(f"Login failed: {e2}", fg="red"), err=True)
            else:
                click.echo(click.style(f"Login failed: {e}", fg="red"), err=True)


def _emit_claude_desktop_snippet(config: Any, config_path: Path) -> None:
    """Print a Claude Desktop JSON snippet for the user to copy."""
    import json
    import shutil

    uv_path = shutil.which("uv") or "<path-to-uv>"

    server_entry: dict[str, Any] = {
        "command": uv_path,
        "args": [
            "--directory",
            str(Path.cwd()),
            "run",
            "synology-mcp",
            "serve",
            "--config",
            str(config_path),
        ],
    }

    # On Linux, include DBUS_SESSION_BUS_ADDRESS so the server process
    # can access the OS keyring (GNOME Keyring / KWallet via D-Bus).
    # Use the env var if set, otherwise construct the standard systemd path.
    if sys.platform == "linux":
        dbus_addr = os.environ.get("DBUS_SESSION_BUS_ADDRESS")
        if not dbus_addr:
            dbus_addr = f"unix:path=/run/user/{os.getuid()}/bus"
        server_entry["env"] = {"DBUS_SESSION_BUS_ADDRESS": dbus_addr}

    snippet = {"mcpServers": {f"synology-{config.display_name}": server_entry}}
    click.echo("\nAdd this to your Claude Desktop config (claude_desktop_config.json):\n")
    click.echo(json.dumps(snippet, indent=2))


@main.command()
@click.option("-c", "--config", type=click.Path(), help="Path to config file")
@click.option("--verbose", is_flag=True, help="Enable debug logging")
def check(config: str | None, verbose: bool) -> None:
    """Validate stored credentials can authenticate."""
    _init_early_logging(verbose=verbose)

    from synology_mcp.core.config import load_config

    try:
        app_config = load_config(config)
    except (FileNotFoundError, ValueError) as e:
        click.echo(click.style(f"Error: {e}", fg="red"), err=True)
        sys.exit(1)

    if not verbose:
        _configure_logging(app_config.logging.level, app_config.logging.file)

    click.echo(f"Checking credentials for '{app_config.display_name}'...")
    asyncio.run(_check_login(app_config))


async def _check_login(config: object) -> None:
    """Validate credentials by attempting a login."""
    from synology_mcp.core.auth import AuthManager
    from synology_mcp.core.client import DsmClient
    from synology_mcp.core.config import AppConfig

    assert isinstance(config, AppConfig)
    assert config.connection is not None

    protocol = "https" if config.connection.https else "http"
    base_url = f"{protocol}://{config.connection.host}:{config.connection.port}"

    async with DsmClient(
        base_url=base_url,
        verify_ssl=config.connection.verify_ssl,
        timeout=config.connection.timeout,
    ) as client:
        await client.query_api_info()

        auth = AuthManager(config, client)
        try:
            await auth.login()
            click.echo(click.style("Authentication successful!", fg="green"))
            await auth.logout()
        except Exception as e:  # noqa: BLE001
            click.echo(click.style(f"Authentication failed: {e}", fg="red"), err=True)
            sys.exit(1)
