"""CLI subcommand: ``python -m cyclone users ...``.

Click-based to match the existing parse-837 / parse-835 convention in
``cyclone.cli``. Provides operator-side user management without going
through the admin HTTP API.

Subcommands
-----------

- ``users create USERNAME --role {admin,user,viewer} [--password PW]``
  Create a user. If ``--password`` is omitted, prompts (with
  confirmation) on the controlling terminal.
- ``users list`` — tab-separated ``id / username / role / state``.
- ``users disable USERNAME`` — set ``disabled_at`` to now.
- ``users reset-password USERNAME [--password PW]`` — replace the hash.
- ``users set-role USERNAME --role {admin,user,viewer}`` — change role.

Exit codes
----------

* 0 — success
* 1 — validation error (unknown username, duplicate username)
* 2 — usage error (missing arg, bad role, short password, unknown
  subcommand). Click itself uses 2 for usage errors so the conventional
  shell tools (``set -e``, etc.) recognize them.

Passwords shorter than 12 chars are rejected everywhere they appear.
"""

from __future__ import annotations

import getpass
import sys

import click

from cyclone.auth import users
from cyclone.auth.permissions import Role
from cyclone.db import SessionLocal

ROLE_CHOICES = [Role.ADMIN.value, Role.USER.value, Role.VIEWER.value]
MIN_PASSWORD_LEN = 12


def _prompt_password(label: str) -> str:
    pw = getpass.getpass(f"{label}: ")
    if not pw:
        click.echo("Password required.", err=True)
        sys.exit(2)
    return pw


def _validate_password(pw: str) -> None:
    if len(pw) < MIN_PASSWORD_LEN:
        click.echo(
            f"Password must be at least {MIN_PASSWORD_LEN} characters.",
            err=True,
        )
        sys.exit(2)


# --------------------------------------------------------------------------- #
# Group
# --------------------------------------------------------------------------- #


@click.group(name="users")
def users_cli() -> None:
    """Manage Cyclone users from the command line."""


# --------------------------------------------------------------------------- #
# create
# --------------------------------------------------------------------------- #


@users_cli.command("create")
@click.argument("username")
@click.option(
    "--role",
    required=True,
    type=click.Choice(ROLE_CHOICES, case_sensitive=False),
    help="Role to grant the new user.",
)
@click.option(
    "--password",
    default=None,
    help=f"Password (min {MIN_PASSWORD_LEN} chars). Prompts if omitted.",
)
def create_user(username: str, role: str, password: str | None) -> None:
    """Create a new user with the given USERNAME and ROLE."""
    pw = password or _prompt_password("Password")
    _validate_password(pw)

    with SessionLocal()() as db:
        if users.get_by_username(db, username) is not None:
            click.echo(f"User '{username}' already exists.", err=True)
            sys.exit(1)
        u = users.create(db, username=username, password=pw, role=role)
        click.echo(f"Created user '{u.username}' with role '{u.role}'.")


# --------------------------------------------------------------------------- #
# list
# --------------------------------------------------------------------------- #


@users_cli.command("list")
def list_users() -> None:
    """List all users as id / username / role / state."""
    from cyclone.db import User

    with SessionLocal()() as db:
        for u in db.query(User).order_by(User.id.asc()).all():
            state = "disabled" if u.disabled_at else "active"
            click.echo(f"{u.id}\t{u.username}\t{u.role}\t{state}")


# --------------------------------------------------------------------------- #
# disable
# --------------------------------------------------------------------------- #


@users_cli.command("disable")
@click.argument("username")
def disable_user(username: str) -> None:
    """Disable USERNAME (sets disabled_at to now)."""
    with SessionLocal()() as db:
        u = users.get_by_username(db, username)
        if u is None:
            click.echo(f"No such user: {username}", err=True)
            sys.exit(1)
        users.disable(db, u.id)
        click.echo(f"Disabled '{username}'.")


# --------------------------------------------------------------------------- #
# reset-password
# --------------------------------------------------------------------------- #


@users_cli.command("reset-password")
@click.argument("username")
@click.option(
    "--password",
    default=None,
    help=f"New password (min {MIN_PASSWORD_LEN} chars). Prompts if omitted.",
)
def reset_password(username: str, password: str | None) -> None:
    """Replace USERNAME's password."""
    pw = password or _prompt_password("New password")
    _validate_password(pw)
    with SessionLocal()() as db:
        u = users.get_by_username(db, username)
        if u is None:
            click.echo(f"No such user: {username}", err=True)
            sys.exit(1)
        users.update_password(db, u.id, pw)
        click.echo(f"Password reset for '{username}'.")


# --------------------------------------------------------------------------- #
# set-role
# --------------------------------------------------------------------------- #


@users_cli.command("set-role")
@click.argument("username")
@click.option(
    "--role",
    required=True,
    type=click.Choice(ROLE_CHOICES, case_sensitive=False),
    help="Role to grant.",
)
def set_role(username: str, role: str) -> None:
    """Change USERNAME's role."""
    with SessionLocal()() as db:
        u = users.get_by_username(db, username)
        if u is None:
            click.echo(f"No such user: {username}", err=True)
            sys.exit(1)
        users.update_role(db, u.id, role)
        click.echo(f"Role for '{username}' set to '{role}'.")
