"""Unit tests for cyclone.auth.users — User CRUD + bcrypt hashing."""

from __future__ import annotations

import pytest
from sqlalchemy import delete
from sqlalchemy.exc import IntegrityError

from cyclone.auth import users
from cyclone.db import SessionLocal, User


@pytest.fixture(autouse=True)
def _clear_users(monkeypatch):
    # conftest sets AUTH_DISABLED=True so pre-auth tests keep passing
    # without a login. The auth-users tests need the real auth path
    # exercised, so flip it back off here.
    monkeypatch.setattr("cyclone.auth.deps.AUTH_DISABLED", False)
    with SessionLocal()() as db:
        db.execute(delete(User))
        db.commit()
    yield
    with SessionLocal()() as db:
        db.execute(delete(User))
        db.commit()


def test_hash_password_returns_bcrypt():
    h = users.hash_password("hunter2hunter2")
    assert h.startswith("$2")


def test_hash_password_produces_unique_salts():
    a = users.hash_password("same-password")
    b = users.hash_password("same-password")
    assert a != b


def test_verify_password_correct():
    h = users.hash_password("hunter2hunter2")
    assert users.verify_password("hunter2hunter2", h) is True


def test_verify_password_incorrect():
    h = users.hash_password("hunter2hunter2")
    assert users.verify_password("WRONG", h) is False


def test_create_user_persists_with_hashed_password():
    with SessionLocal()() as db:
        u = users.create(db, username="alice", password="hunter2hunter2", role="admin")
    assert u.id is not None
    assert u.username == "alice"
    assert u.role == "admin"
    assert u.disabled_at is None
    assert u.password_hash != "hunter2hunter2"
    assert u.password_hash.startswith("$2")


def test_create_user_rejects_duplicate_username():
    with SessionLocal()() as db:
        users.create(db, username="bob", password="hunter2hunter2", role="user")
    with SessionLocal()() as db:
        with pytest.raises(IntegrityError):
            users.create(db, username="bob", password="anotherone", role="user")


def test_get_by_username_returns_user():
    with SessionLocal()() as db:
        users.create(db, username="carol", password="hunter2hunter2", role="viewer")
    with SessionLocal()() as db:
        u = users.get_by_username(db, "carol")
    assert u is not None
    assert u.username == "carol"


def test_get_by_username_returns_none_for_missing():
    with SessionLocal()() as db:
        assert users.get_by_username(db, "ghost") is None


def test_disable_user_sets_disabled_at():
    with SessionLocal()() as db:
        u = users.create(db, username="dave", password="hunter2hunter2", role="user")
        users.disable(db, u.id)
    with SessionLocal()() as db:
        refreshed = users.get_by_username(db, "dave")
    assert refreshed.disabled_at is not None


def test_to_public_shape_omits_password_hash():
    with SessionLocal()() as db:
        u = users.create(db, username="eve", password="hunter2hunter2", role="admin")
        shape = users.to_public(u)
    assert "password_hash" not in shape
    assert shape["username"] == "eve"
    assert shape["role"] == "admin"
    assert "id" in shape and "createdAt" in shape


def test_update_password_actually_rehashes_and_verifies():
    with SessionLocal()() as db:
        u = users.create(db, username="frank", password="hunter2hunter2", role="user")
    with SessionLocal()() as db:
        users.update_password(db, u.id, "newpassword1")
    # Old password no longer verifies.
    with SessionLocal()() as db:
        refreshed = users.get_by_username(db, "frank")
    assert not users.verify_password("hunter2hunter2", refreshed.password_hash)
    assert users.verify_password("newpassword1", refreshed.password_hash)