"""Auth bootstrap reads CYCLONE_ADMIN_*_FILE env vars when set.

Mirrors the Docker-secret posture in ``docker-compose.yml``: secrets
mounted at ``/run/secrets/<name>`` with the ``*_FILE`` env var pointing
at the path. The bootstrap should prefer the file over the bare env var
when both are present (file = Docker secret wins).
"""
from __future__ import annotations

from pathlib import Path

import pytest

from cyclone import db
from cyclone.auth import bootstrap, users
from cyclone.auth.permissions import Role


@pytest.fixture
def tmp_db(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    db_path = tmp_path / "test.db"
    monkeypatch.setenv("CYCLONE_DB_URL", f"sqlite:///{db_path}")
    db.init_db()
    return db_path


def _write_secrets(tmp_path: Path, *, username: str, password: str) -> tuple[Path, Path]:
    user_file = tmp_path / "admin_username"
    pw_file = tmp_path / "admin_pw"
    user_file.write_text(f"{username}\n")
    pw_file.write_text(f"{password}\n")
    return user_file, pw_file


def test_bootstrap_creates_admin_from_file_env_vars(
    tmp_path: Path, tmp_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    user_file, pw_file = _write_secrets(
        tmp_path, username="deploy-admin", password="super-secret-password-123"
    )
    monkeypatch.delenv("CYCLONE_ADMIN_USERNAME", raising=False)
    monkeypatch.delenv("CYCLONE_ADMIN_PASSWORD", raising=False)
    monkeypatch.setenv("CYCLONE_ADMIN_USERNAME_FILE", str(user_file))
    monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD_FILE", str(pw_file))

    bootstrap.run()

    with db.SessionLocal()() as session:
        user = users.get_by_username(session, "deploy-admin")
    assert user is not None
    assert user.role == Role.ADMIN.value
    assert users.verify_password("super-secret-password-123", user.password_hash)


def test_file_env_var_overrides_plain_env_var(
    tmp_path: Path, tmp_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """When both _FILE and bare env vars are set, _FILE wins."""
    user_file, pw_file = _write_secrets(
        tmp_path, username="file-user", password="file-password-12345"
    )
    monkeypatch.setenv("CYCLONE_ADMIN_USERNAME", "env-user")
    monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD", "env-password-12345")
    monkeypatch.setenv("CYCLONE_ADMIN_USERNAME_FILE", str(user_file))
    monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD_FILE", str(pw_file))

    bootstrap.run()

    with db.SessionLocal()() as session:
        file_user = users.get_by_username(session, "file-user")
        env_user = users.get_by_username(session, "env-user")
    assert file_user is not None
    assert env_user is None, "bare env var should be ignored when _FILE is set"


def test_bootstrap_falls_back_to_plain_env_var(
    tmp_path: Path, tmp_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.delenv("CYCLONE_ADMIN_USERNAME_FILE", raising=False)
    monkeypatch.delenv("CYCLONE_ADMIN_PASSWORD_FILE", raising=False)
    monkeypatch.setenv("CYCLONE_ADMIN_USERNAME", "env-user")
    monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD", "env-password-12345")

    bootstrap.run()

    with db.SessionLocal()() as session:
        user = users.get_by_username(session, "env-user")
    assert user is not None
    assert user.role == Role.ADMIN.value


def test_bootstrap_strips_trailing_whitespace_from_file(
    tmp_path: Path, tmp_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    """Secret files often have a trailing newline from `printf`/`echo`.
    The bootstrap must strip it so bcrypt verify doesn't see whitespace."""
    user_file = tmp_path / "admin_username"
    pw_file = tmp_path / "admin_pw"
    user_file.write_text("deploy-admin\n\n")
    pw_file.write_text("super-secret-password-123\n")
    monkeypatch.delenv("CYCLONE_ADMIN_USERNAME", raising=False)
    monkeypatch.delenv("CYCLONE_ADMIN_PASSWORD", raising=False)
    monkeypatch.setenv("CYCLONE_ADMIN_USERNAME_FILE", str(user_file))
    monkeypatch.setenv("CYCLONE_ADMIN_PASSWORD_FILE", str(pw_file))

    bootstrap.run()

    with db.SessionLocal()() as session:
        user = users.get_by_username(session, "deploy-admin")
    assert user is not None
    # Should verify cleanly with no trailing whitespace.
    assert users.verify_password("super-secret-password-123", user.password_hash)
    assert not users.verify_password(
        "super-secret-password-123\n", user.password_hash
    )


def test_bootstrap_raises_when_file_path_missing(
    tmp_path: Path, tmp_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.delenv("CYCLONE_ADMIN_USERNAME", raising=False)
    monkeypatch.delenv("CYCLONE_ADMIN_PASSWORD", raising=False)
    monkeypatch.setenv(
        "CYCLONE_ADMIN_USERNAME_FILE", str(tmp_path / "does-not-exist")
    )
    monkeypatch.setenv(
        "CYCLONE_ADMIN_PASSWORD_FILE", str(tmp_path / "also-missing")
    )

    with pytest.raises(RuntimeError, match="failed to read"):
        bootstrap.run()
