"""AuthService behaviour with a mocked TokenVerifier (no network / real JWT)."""

from __future__ import annotations

from unittest.mock import MagicMock

import pytest

from tradekartauth.config import CLAIM_GROUPS, CognitoAuthConfig
from tradekartauth.exceptions import AuthenticationError, AuthorizationError, TokenError
from tradekartauth.services.auth_service import AuthService
from tradekartauth.services.token_verifier import TokenVerifier


def _config(**kwargs: object) -> CognitoAuthConfig:
    d: dict = {"user_pool_id": "ap-south-1_x", "region": "ap-south-1"}
    d.update(kwargs)
    return CognitoAuthConfig(**d)


def test_authenticate_missing_token() -> None:
    svc = AuthService(_config(), token_verifier=MagicMock())
    r = svc.authenticate(None)
    assert not r.ok
    assert isinstance(r.error, AuthenticationError)


def test_authenticate_verifier_raises_token_error() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.side_effect = TokenError("expired")
    svc = AuthService(_config(), token_verifier=mock_v)
    r = svc.authenticate("Bearer x")
    assert not r.ok
    assert isinstance(r.error, TokenError)


def test_authenticate_success() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.return_value = {
        "sub": "u1",
        "token_use": "access",
        CLAIM_GROUPS: ["trader"],
    }
    svc = AuthService(_config(), token_verifier=mock_v)
    r = svc.authenticate("tok")
    assert r.ok
    assert r.role == "trader"
    assert r.claims is not None


def test_get_role_returns_none_on_failure() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.side_effect = TokenError("bad")
    svc = AuthService(_config(), token_verifier=mock_v)
    assert svc.get_role("t") is None


def test_has_permission_false_when_auth_fails() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.side_effect = TokenError("bad")
    svc = AuthService(_config(), token_verifier=mock_v)
    assert svc.has_permission("t", "auth:read") is False


def test_has_permission_false_when_role_lacks_perm() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.return_value = {CLAIM_GROUPS: ["viewer"]}
    svc = AuthService(_config(), token_verifier=mock_v)
    assert svc.has_permission("t", "trade:place_order") is False


def test_has_permission_true() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.return_value = {CLAIM_GROUPS: ["trader"]}
    svc = AuthService(_config(), token_verifier=mock_v)
    assert svc.has_permission("t", "trade:place_order") is True


def test_require_permission_raises_when_no_token() -> None:
    svc = AuthService(_config(), token_verifier=MagicMock())
    with pytest.raises(AuthenticationError):
        svc.require_permission(None, "auth:read")


def test_require_permission_raises_when_no_role_in_claims() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.return_value = {"sub": "x"}  # no cognito:groups
    svc = AuthService(_config(), token_verifier=mock_v)
    with pytest.raises(AuthenticationError, match="No role found"):
        svc.require_permission("t", "auth:read")


def test_require_permission_raises_authorization() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    mock_v.verify.return_value = {CLAIM_GROUPS: ["viewer"]}
    svc = AuthService(_config(), token_verifier=mock_v)
    with pytest.raises(AuthorizationError):
        svc.require_permission("t", "trade:place_order")


def test_require_permission_returns_claims() -> None:
    mock_v = MagicMock(spec=TokenVerifier)
    claims = {CLAIM_GROUPS: ["admin"]}
    mock_v.verify.return_value = claims
    svc = AuthService(_config(), token_verifier=mock_v)
    out = svc.require_permission("t", "auth:read")
    assert out == claims


def test_auth_service_instantiates_token_verifier_when_omitted() -> None:
    """Regression: verifier must be an instance, not the class."""
    svc = AuthService(_config())
    assert isinstance(svc._token_verifier, TokenVerifier)
