import os
from pathlib import Path

import click
from sqlalchemy.exc import IntegrityError
from sqlalchemy import select

from l4d2web.auth import hash_password, validate_new_password
from l4d2web.db import session_scope
from l4d2web.models import Job, Overlay, User
from l4d2web.services.overlay_creation import (
    create_overlay_directory,
    generate_overlay_path,
)


@click.command("promote-admin")
@click.argument("username")
def promote_admin(username: str) -> None:
    with session_scope() as db:
        user = db.scalar(select(User).where(User.username == username))
        if user is None:
            raise click.ClickException("user not found")
        user.admin = True


@click.command("create-user")
@click.argument("username")
@click.option("--admin", is_flag=True, default=False)
def create_user(username: str, admin: bool) -> None:
    password = os.getenv("LEFT4ME_ADMIN_PASSWORD")
    if password is None:
        password = click.prompt("Password", hide_input=True, confirmation_prompt=True)
    policy_error = validate_new_password(password)
    if policy_error is not None:
        raise click.ClickException(policy_error)

    try:
        with session_scope() as db:
            existing = db.scalar(select(User).where(User.username == username))
            if existing is not None:
                raise click.ClickException("user already exists")
            db.add(User(username=username, password_digest=hash_password(password), admin=admin))
    except IntegrityError as exc:
        raise click.ClickException("user already exists") from exc

    click.echo(f"created user {username}")


@click.command("seed-script-overlays")
@click.argument(
    "directory",
    type=click.Path(exists=True, file_okay=False, path_type=Path),
)
def seed_script_overlays(directory: Path) -> None:
    """Upsert one system-wide script overlay per *.sh file in DIRECTORY.

    Overlay name = filename stem; user_id stays NULL. Existing rows by name
    have their script refreshed in place. Hard-errors if a name collides
    with a non-script overlay.
    """
    sh_files = sorted(p for p in directory.glob("*.sh") if p.stem)
    if not sh_files:
        click.echo(f"no *.sh files in {directory}", err=True)
        return

    with session_scope() as db:
        for sh in sh_files:
            name = sh.stem
            content = sh.read_text()
            existing = db.scalar(
                select(Overlay).where(Overlay.name == name, Overlay.user_id.is_(None))
            )
            if existing is not None:
                if existing.type != "script":
                    raise click.ClickException(
                        f"overlay {name!r} exists but is type={existing.type!r}, not script"
                    )
                existing.script = content
                click.echo(f"updated {name} (id={existing.id})")
            else:
                overlay = Overlay(
                    name=name, path="", type="script", user_id=None, script=content
                )
                db.add(overlay)
                db.flush()
                overlay.path = generate_overlay_path(overlay.id)
                db.flush()
                create_overlay_directory(overlay)
                click.echo(f"created {name} (id={overlay.id})")


@click.command("workshop-refresh")
def workshop_refresh() -> None:
    """Enqueue a global workshop refresh job. Idempotent: if a refresh is
    already queued or running, prints its id and exits 0."""
    with session_scope() as db:
        existing = db.scalar(
            select(Job)
            .where(
                Job.operation == "refresh_workshop_items",
                Job.state.in_(("queued", "running", "cancelling")),
            )
            .order_by(Job.id.desc())
            .limit(1)
        )
        if existing is not None:
            click.echo(
                f"refresh_workshop_items job {existing.id} already {existing.state}"
            )
            return
        job = Job(
            user_id=None,
            server_id=None,
            operation="refresh_workshop_items",
            state="queued",
        )
        db.add(job)
        db.flush()
        click.echo(f"enqueued refresh_workshop_items job {job.id}")


def register_cli(app) -> None:
    app.cli.add_command(promote_admin)
    app.cli.add_command(create_user)
    app.cli.add_command(seed_script_overlays)
    app.cli.add_command(workshop_refresh)
