"""TokenVerifier with mocked JWKS + jwt.decode (no real Cognito)."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest

from tradekartauth.config import CLAIM_AUD, CLAIM_CLIENT_ID, CLAIM_TOKEN_USE, CognitoAuthConfig
from tradekartauth.exceptions import TokenError
from tradekartauth.services.token_verifier import TokenVerifier


def _cfg(app_client_id: str | None = "cid") -> CognitoAuthConfig:
    return CognitoAuthConfig(
        user_pool_id="ap-south-1_pool",
        region="ap-south-1",
        app_client_id=app_client_id,
    )


@patch("tradekartauth.services.token_verifier.jwt.decode")
def test_verify_access_token_client_match(mock_decode: MagicMock) -> None:
    payload = {
        "token_use": "access",
        "client_id": "cid",
        "sub": "u",
    }
    mock_decode.return_value = payload
    jwk = MagicMock()
    jwk.get_signing_key_from_jwt.return_value = MagicMock(key="k")

    tv = TokenVerifier(_cfg(), jwk_client=jwk)
    out = tv.verify("header.payload.sig")

    assert out == payload
    mock_decode.assert_called_once()
    jwk.get_signing_key_from_jwt.assert_called_once_with("header.payload.sig")


@patch("tradekartauth.services.token_verifier.jwt.decode")
def test_verify_id_token_aud_match(mock_decode: MagicMock) -> None:
    payload = {
        "token_use": "id",
        "aud": "cid",
    }
    mock_decode.return_value = payload
    jwk = MagicMock()
    jwk.get_signing_key_from_jwt.return_value = MagicMock(key="k")

    tv = TokenVerifier(_cfg(), jwk_client=jwk)
    assert tv.verify("t") == payload


def test_client_mismatch_access_token() -> None:
    jwk = MagicMock()
    jwk.get_signing_key_from_jwt.return_value = MagicMock(key="k")
    with patch("tradekartauth.services.token_verifier.jwt.decode") as mock_decode:
        mock_decode.return_value = {
            CLAIM_TOKEN_USE: "access",
            CLAIM_CLIENT_ID: "wrong",
        }
        tv = TokenVerifier(_cfg(app_client_id="cid"), jwk_client=jwk)
        with pytest.raises(TokenError, match="client ID mismatch"):
            tv.verify("t")


def test_client_mismatch_id_token() -> None:
    jwk = MagicMock()
    jwk.get_signing_key_from_jwt.return_value = MagicMock(key="k")
    with patch("tradekartauth.services.token_verifier.jwt.decode") as mock_decode:
        mock_decode.return_value = {
            CLAIM_TOKEN_USE: "id",
            CLAIM_AUD: "wrong-aud",
        }
        tv = TokenVerifier(_cfg(app_client_id="cid"), jwk_client=jwk)
        with pytest.raises(TokenError, match="ID token client ID mismatch"):
            tv.verify("t")


def test_unknown_token_use_when_client_id_set() -> None:
    jwk = MagicMock()
    jwk.get_signing_key_from_jwt.return_value = MagicMock(key="k")
    with patch("tradekartauth.services.token_verifier.jwt.decode") as mock_decode:
        mock_decode.return_value = {CLAIM_TOKEN_USE: "refresh"}
        tv = TokenVerifier(_cfg(app_client_id="cid"), jwk_client=jwk)
        with pytest.raises(TokenError, match="Unknown token use"):
            tv.verify("t")


def test_jwk_raises_pyjwt_error() -> None:
    jwk = MagicMock()
    jwk.get_signing_key_from_jwt.side_effect = __import__("jwt").PyJWTError("nope")
    tv = TokenVerifier(_cfg(app_client_id=None), jwk_client=jwk)
    with pytest.raises(TokenError, match="JWT verification failed"):
        tv.verify("bad")
