"""Unit tests for the security adapters (no DB, no network)."""

import datetime as dt
import uuid

import jwt
import pytest
from app.core.config import Settings
from app.core.security import Argon2PasswordHasher, JwtTokenService
from app.domain.errors import AuthenticationError
from app.domain.tokens import TokenType


def _settings(**overrides: object) -> Settings:
    base: dict[str, object] = {"jwt_secret": "unit-test-secret", "access_token_ttl_seconds": 900}
    base.update(overrides)
    return Settings(**base)  # type: ignore[arg-type]


def test_password_hash_roundtrip() -> None:
    hasher = Argon2PasswordHasher()
    hashed = hasher.hash("correct horse battery staple")
    assert hashed != "correct horse battery staple"

    valid, updated = hasher.verify_and_update("correct horse battery staple", hashed)
    assert valid is True
    assert updated is None  # fresh hash, no rehash needed

    wrong, _ = hasher.verify_and_update("wrong password", hashed)
    assert wrong is False


def test_jwt_issue_and_decode_roundtrip() -> None:
    svc = JwtTokenService(_settings())
    subject = uuid.uuid4()

    issued = svc.issue(subject=subject, token_type=TokenType.ACCESS)
    claims = svc.decode(issued.encoded)

    assert claims.subject == subject
    assert claims.token_type is TokenType.ACCESS
    assert claims.jti == issued.jti


def test_jwt_rejects_tampered_token() -> None:
    svc = JwtTokenService(_settings())
    issued = svc.issue(subject=uuid.uuid4(), token_type=TokenType.ACCESS)
    tampered = issued.encoded[:-2] + ("aa" if issued.encoded[-2:] != "aa" else "bb")

    with pytest.raises(AuthenticationError):
        svc.decode(tampered)


def test_jwt_rejects_wrong_secret() -> None:
    issuer = JwtTokenService(_settings(jwt_secret="secret-a"))
    verifier = JwtTokenService(_settings(jwt_secret="secret-b"))
    issued = issuer.issue(subject=uuid.uuid4(), token_type=TokenType.ACCESS)

    with pytest.raises(AuthenticationError):
        verifier.decode(issued.encoded)


def test_jwt_rejects_expired_token() -> None:
    settings = _settings()
    secret = settings.jwt_secret.get_secret_value()
    expired = jwt.encode(
        {
            "sub": str(uuid.uuid4()),
            "type": "access",
            "jti": str(uuid.uuid4()),
            "iat": int((dt.datetime.now(dt.UTC) - dt.timedelta(hours=2)).timestamp()),
            "exp": int((dt.datetime.now(dt.UTC) - dt.timedelta(hours=1)).timestamp()),
        },
        secret,
        algorithm=settings.jwt_algorithm,
    )
    with pytest.raises(AuthenticationError):
        JwtTokenService(settings).decode(expired)
