"""
CLR client-side validation tests.

Tests that validation happens in the resource layer (before transport)
using a stub transport. Mirrors the TS SDK validation.unit.test.ts.
"""

from __future__ import annotations

from typing import Any

import pytest

from timeback_clr.resources.credentials import CredentialsResource
from timeback_common import InputValidationError


class _StubTransport:
    """Minimal stub transport that records requests without making HTTP calls."""

    def __init__(self) -> None:
        self.request_count = 0

    class paths:
        credentials = "/ims/clr/v2p0/credentials"
        discovery = "/ims/clr/v2p0/discovery"

    async def post(self, _path: str, _data: Any, **_kwargs: Any) -> dict[str, Any]:
        self.request_count += 1
        return _valid_credential_response()

    async def get(self, _path: str, **_kwargs: Any) -> dict[str, Any]:
        self.request_count += 1
        return {}

    async def close(self) -> None:
        pass


def _valid_credential() -> dict[str, Any]:
    return {
        "@context": [
            "https://www.w3.org/ns/credentials/v2",
            "https://purl.imsglobal.org/spec/clr/v2p0/context-2.0.1",
            "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.jsonld",
        ],
        "id": "urn:uuid:test-credential",
        "type": ["VerifiableCredential", "ClrCredential"],
        "issuer": {
            "id": "https://example.edu",
            "type": ["Profile"],
            "name": "Test University",
        },
        "name": "Test Credential",
        "validFrom": "2024-01-01T00:00:00Z",
        "credentialSubject": {
            "type": ["ClrSubject"],
            "verifiableCredential": [
                {
                    "@context": [
                        "https://www.w3.org/ns/credentials/v2",
                        "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
                    ],
                    "id": "urn:uuid:nested-credential",
                    "type": ["VerifiableCredential", "AchievementCredential"],
                    "issuer": {"id": "https://example.edu", "type": ["Profile"]},
                    "validFrom": "2024-01-01T00:00:00Z",
                    "credentialSubject": {},
                },
            ],
        },
    }


def _valid_credential_response() -> dict[str, Any]:
    cred = _valid_credential()
    cred["proof"] = [
        {
            "type": "DataIntegrityProof",
            "proofPurpose": "assertionMethod",
            "verificationMethod": "did:example:123#key-1",
            "created": "2024-01-01T00:00:00Z",
            "proofValue": "z123abc",
        }
    ]
    return cred


class TestClrCredentialValidation:
    """Tests that invalid credentials are rejected before reaching the transport."""

    @pytest.mark.asyncio
    async def test_rejects_empty_context(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["@context"] = []
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_id(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["id"] = ""
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_type_array(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["type"] = []
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_name(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["name"] = ""
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_valid_from(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["validFrom"] = ""
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_issuer_id(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["issuer"]["id"] = ""
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_issuer_type(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["issuer"]["type"] = []
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_verifiable_credential_array(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["credentialSubject"]["verifiableCredential"] = []
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_empty_subject_type(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["credentialSubject"]["type"] = []
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_nested_empty_context(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["credentialSubject"]["verifiableCredential"][0]["@context"] = []
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_rejects_nested_empty_id(self) -> None:
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        cred["credentialSubject"]["verifiableCredential"][0]["id"] = ""
        with pytest.raises(InputValidationError):
            await resource.upsert(cred)
        assert transport.request_count == 0

    @pytest.mark.asyncio
    async def test_valid_credential_reaches_transport(self) -> None:
        """Valid credential should pass validation and reach the transport."""
        transport = _StubTransport()
        resource = CredentialsResource(transport)  # type: ignore[arg-type]
        cred = _valid_credential()
        result = await resource.upsert(cred)
        assert transport.request_count == 1
        assert result.id == "urn:uuid:test-credential"
