"""Helpers for DFP's own account tests (imported, never collected).

Everything runs in-process on the in-memory SQLite from `tests/kit/_env.py`: no server, no
network, no Postgres. `fresh()` installs the engine, recreates the tables and resets the
in-process throttles; `person()` inserts a principal with an optional password, level and
status straight into the tables; `client()` is a TestClient over https with the Origin
header every non-safe request must carry. The password constant here is a test fixture,
not a secret: it never leaves the process.
"""

from __future__ import annotations

from fastapi.testclient import TestClient
from sqlalchemy import select

from app.config import settings
from app.main import app
from app.models import Account, AccountCredential, AccountLevel, AccountMember, AuditLog
from app.routers import auth as auth_router
from app.services import accounts, mail, passwords, sessions
from tests.kit import _env

BASE = "https://testserver"
PASSWORD = "correct horse battery staple"

# The suite runs argon2 at a fraction of the production cost (the production parameters are
# pinned by tests/test_accounts_passwords.py against the module constants, which this leaves
# alone): every code path is the real one, and a suite that paid 70 ms per verify crawled.
from argon2 import PasswordHasher  # noqa: E402

passwords._hasher = PasswordHasher(time_cost=1, memory_cost=8 * 1024, parallelism=1)
passwords._DUMMY_HASH = passwords._hasher.hash("test-dummy")
PASSWORD_HASH = passwords.hash_password(PASSWORD)


class RecordingMailer:
    def __init__(self) -> None:
        self.sent: list[tuple[str, str, str]] = []

    def send(self, to: str, subject: str, text: str) -> None:
        self.sent.append((to, subject, text))


RECORDER: RecordingMailer | None = None
THROTTLES = (auth_router.login_throttle, auth_router.unknown_throttle,
             auth_router.forgot_throttle, auth_router.redeem_throttle)


def fresh(monkeypatch, *, owner: str = "rian", mailer: RecordingMailer | None = None) -> RecordingMailer:
    _env.install()
    _env.reset()
    monkeypatch.setattr(settings, "account_owner", owner)
    monkeypatch.setattr(settings, "public_base_url", BASE)
    accounts.init()
    for throttle in THROTTLES:
        throttle._keys.clear()
        throttle._all.clear()
    global RECORDER
    recorder = mailer or RecordingMailer()
    monkeypatch.setattr(mail, "_override", recorder)
    RECORDER = recorder
    return recorder


def person(username: str, *, password: str | None = PASSWORD, level: str | None = None,
           status: str = "active", email: str | None = None, display_name: str | None = None,
           must_change: bool = False) -> int:
    with _env.TestSessionLocal() as db:
        account = Account(username=username, email=email or f"{username}@example.com",
                          display_name=display_name or username.title(), status=status)
        db.add(account)
        db.flush()
        if password is not None or status == "invited":
            db.add(AccountCredential(
                account_id=account.id,
                password_hash=(PASSWORD_HASH if password == PASSWORD else passwords.hash_password(password)) if password else None,
                must_change_password=must_change,
            ))
        if level:
            if db.get(AccountLevel, level) is None:
                db.add(AccountLevel(name=level, permissions=[], assignable=[]))
                db.flush()
            db.add(AccountMember(username=username, level=level, active=True))
        db.commit()
        return account.id


def client(**headers) -> TestClient:
    return TestClient(app, base_url=BASE, headers={"Origin": BASE, **headers})


def login(c: TestClient, username: str, password: str = PASSWORD, **headers):
    return c.post("/api/auth/login", json={"username": username, "password": password}, headers=headers)


def as_user(c: TestClient, username: str) -> str:
    """A session row minted directly, its cookie set on the client; returns the token hash."""
    with _env.TestSessionLocal() as db:
        account_id = db.scalar(select(Account.id).where(Account.username == username))
    assert account_id is not None, username
    token = sessions.mint(account_id, ip="127.0.0.1", user_agent="tests")
    c.cookies.jar.clear()
    c.cookies.set(sessions.cookie_name(), token)
    return sessions.hash_token(token)


def audit_actions() -> list[str]:
    with _env.TestSessionLocal() as db:
        return [a for (a,) in db.execute(select(AuditLog.action).order_by(AuditLog.id)).all()]
