"""Tests for Passport validator."""

from __future__ import annotations

import time

import jwt
import pytest

from ai4gh.core.exceptions import AuthError
from ai4gh.specs.passports.models import VisaType
from ai4gh.specs.passports.validator import PassportValidator


def _make_passport_jwt(
    visas: list[dict] | None = None,
    exp: int | None = None,
) -> str:
    """Create a test Passport JWT (unsigned, for testing only)."""
    payload: dict = {
        "iss": "https://idp.example.com",
        "sub": "user@example.com",
        "iat": int(time.time()),
    }
    if exp is not None:
        payload["exp"] = exp
    if visas is not None:
        payload["ga4gh_passport_v1"] = visas
    return jwt.encode(payload, "secret", algorithm="HS256")


@pytest.fixture
def validator() -> PassportValidator:
    return PassportValidator(algorithms=["HS256"])


class TestPassportValidator:
    def test_decode_basic(self, validator: PassportValidator) -> None:
        token = _make_passport_jwt()
        claims = validator.decode(token, verify=False)
        assert claims.iss == "https://idp.example.com"
        assert claims.sub == "user@example.com"

    def test_decode_with_visas(self, validator: PassportValidator) -> None:
        visas = [
            {
                "type": "ResearcherStatus",
                "value": "https://example.com",
                "source": "https://idp.example.com",
            }
        ]
        token = _make_passport_jwt(visas=visas)
        claims = validator.decode(token, verify=False)
        assert len(claims.ga4gh_passport_v1) == 1
        assert claims.ga4gh_passport_v1[0].type is VisaType.RESEARCHER_STATUS

    def test_decode_invalid_token(self, validator: PassportValidator) -> None:
        with pytest.raises(AuthError, match="Invalid Passport token"):
            validator.decode("not-a-jwt", verify=False)

    def test_extract_visas_all(self, validator: PassportValidator) -> None:
        visas = [
            {
                "type": "ResearcherStatus",
                "value": "v1",
                "source": "https://idp.example.com",
            },
            {
                "type": "ControlledAccessGrants",
                "value": "v2",
                "source": "https://dac.example.com",
            },
        ]
        token = _make_passport_jwt(visas=visas)
        claims = validator.decode(token, verify=False)
        extracted = validator.extract_visas(claims)
        assert len(extracted) == 2

    def test_extract_visas_by_type(self, validator: PassportValidator) -> None:
        visas = [
            {
                "type": "ResearcherStatus",
                "value": "v1",
                "source": "https://idp.example.com",
            },
            {
                "type": "ControlledAccessGrants",
                "value": "v2",
                "source": "https://dac.example.com",
            },
        ]
        token = _make_passport_jwt(visas=visas)
        claims = validator.decode(token, verify=False)
        extracted = validator.extract_visas(claims, visa_type=VisaType.CONTROLLED_ACCESS_GRANTS)
        assert len(extracted) == 1
        assert extracted[0].value == "v2"

    def test_extract_visas_by_source(self, validator: PassportValidator) -> None:
        visas = [
            {
                "type": "ResearcherStatus",
                "value": "v1",
                "source": "https://idp.example.com",
            },
            {
                "type": "ControlledAccessGrants",
                "value": "v2",
                "source": "https://dac.example.com",
            },
        ]
        token = _make_passport_jwt(visas=visas)
        claims = validator.decode(token, verify=False)
        extracted = validator.extract_visas(claims, source="https://dac.example.com")
        assert len(extracted) == 1

    def test_is_expired_false(self, validator: PassportValidator) -> None:
        token = _make_passport_jwt(exp=int(time.time()) + 3600)
        claims = validator.decode(token, verify=False)
        assert validator.is_expired(claims) is False

    def test_is_expired_true(self, validator: PassportValidator) -> None:
        token = _make_passport_jwt(exp=int(time.time()) - 3600)
        claims = validator.decode(token, verify=False)
        assert validator.is_expired(claims) is True

    def test_is_expired_no_exp(self, validator: PassportValidator) -> None:
        token = _make_passport_jwt()
        claims = validator.decode(token, verify=False)
        assert validator.is_expired(claims) is False

    def test_evaluate_access_granted(self, validator: PassportValidator) -> None:
        visas = [
            {
                "type": "ControlledAccessGrants",
                "value": "https://dataset.example.com/ds001",
                "source": "https://dac.example.com",
            }
        ]
        token = _make_passport_jwt(visas=visas, exp=int(time.time()) + 3600)
        claims = validator.decode(token, verify=False)
        decision = validator.evaluate_access(
            claims,
            required_type=VisaType.CONTROLLED_ACCESS_GRANTS,
            required_value="https://dataset.example.com/ds001",
        )
        assert decision.granted is True
        assert len(decision.matching_visas) == 1

    def test_evaluate_access_denied_no_visa(self, validator: PassportValidator) -> None:
        visas = [
            {
                "type": "ResearcherStatus",
                "value": "v1",
                "source": "https://idp.example.com",
            }
        ]
        token = _make_passport_jwt(visas=visas, exp=int(time.time()) + 3600)
        claims = validator.decode(token, verify=False)
        decision = validator.evaluate_access(
            claims,
            required_type=VisaType.CONTROLLED_ACCESS_GRANTS,
        )
        assert decision.granted is False

    def test_evaluate_access_denied_expired(self, validator: PassportValidator) -> None:
        visas = [
            {
                "type": "ControlledAccessGrants",
                "value": "ds001",
                "source": "https://dac.example.com",
            }
        ]
        token = _make_passport_jwt(visas=visas, exp=int(time.time()) - 3600)
        claims = validator.decode(token, verify=False)
        decision = validator.evaluate_access(
            claims,
            required_type=VisaType.CONTROLLED_ACCESS_GRANTS,
        )
        assert decision.granted is False
        assert "expired" in decision.reason
