"""
Tests for LockStockClient wrapped DEK and payload blob storage methods.

Uses httpx mock transport to avoid real network calls.
"""

import base64
import json
import pytest
import httpx

from lockstock_core.client import LockStockClient


# --- Mock HTTP transport ---

class MockTransport(httpx.AsyncBaseTransport):
    """Mock transport that records requests and returns canned responses."""

    def __init__(self):
        self.requests = []
        self.responses = {}  # (method, path) -> (status, json_body)

    def add_response(self, method: str, path: str, status: int = 200, body: dict = None):
        self.responses[(method.upper(), path)] = (status, body or {})

    async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
        self.requests.append(request)
        method = request.method
        path = request.url.path

        key = (method, path)
        if key in self.responses:
            status, body = self.responses[key]
            return httpx.Response(
                status_code=status,
                json=body,
                request=request,
            )

        # Default: 404
        return httpx.Response(
            status_code=404,
            json={"error": "not_found"},
            request=request,
        )


@pytest.fixture
def mock_transport():
    return MockTransport()


@pytest.fixture
def client(mock_transport):
    c = LockStockClient(
        agent_id="agent_test_001",
        api_key="lsk_test_key_abc123",
        endpoint="https://lockstock.test",
    )
    # Replace internal httpx client with mock
    c._client = httpx.AsyncClient(transport=mock_transport)
    c._last_known_sequence = 0  # Skip lazy sync
    return c


class TestStoreWrappedDEK:
    """Test store_wrapped_dek method."""

    @pytest.mark.asyncio
    async def test_sends_put_with_correct_path(self, client, mock_transport):
        """PUT to /v1/agents/{agent_id}/keys/{version}."""
        mock_transport.add_response(
            "PUT", "/v1/agents/agent_test_001/keys/1",
            status=200,
            body={"stored": True, "agent_id": "agent_test_001", "key_version": 1},
        )

        result = await client.store_wrapped_dek("dGVzdA==", key_version=1)

        assert result["stored"] is True
        assert len(mock_transport.requests) == 1
        req = mock_transport.requests[0]
        assert req.method == "PUT"
        assert "/v1/agents/agent_test_001/keys/1" in str(req.url)

    @pytest.mark.asyncio
    async def test_sends_bearer_auth(self, client, mock_transport):
        """Authorization header contains Bearer token."""
        mock_transport.add_response(
            "PUT", "/v1/agents/agent_test_001/keys/1",
            status=200,
            body={"stored": True, "agent_id": "agent_test_001", "key_version": 1},
        )

        await client.store_wrapped_dek("dGVzdA==", key_version=1)

        req = mock_transport.requests[0]
        assert req.headers["authorization"] == "Bearer lsk_test_key_abc123"

    @pytest.mark.asyncio
    async def test_sends_wrapped_dek_in_body(self, client, mock_transport):
        """Request body contains wrapped_dek_b64."""
        mock_transport.add_response(
            "PUT", "/v1/agents/agent_test_001/keys/2",
            status=200,
            body={"stored": True, "agent_id": "agent_test_001", "key_version": 2},
        )

        wrapped_b64 = base64.b64encode(b"x" * 60).decode()
        await client.store_wrapped_dek(wrapped_b64, key_version=2)

        req = mock_transport.requests[0]
        body = json.loads(req.content)
        assert body["wrapped_dek_b64"] == wrapped_b64


class TestGetWrappedDEK:
    """Test get_wrapped_dek method."""

    @pytest.mark.asyncio
    async def test_retrieves_specific_version(self, client, mock_transport):
        """GET /v1/agents/{agent_id}/keys/{version}."""
        mock_transport.add_response(
            "GET", "/v1/agents/agent_test_001/keys/1",
            status=200,
            body={
                "agent_id": "agent_test_001",
                "key_version": 1,
                "wrapped_dek_b64": "dGVzdA==",
                "created_at": "2026-02-15T00:00:00Z",
            },
        )

        result = await client.get_wrapped_dek(key_version=1)

        assert result is not None
        assert result["key_version"] == 1
        assert result["wrapped_dek_b64"] == "dGVzdA=="

    @pytest.mark.asyncio
    async def test_retrieves_latest(self, client, mock_transport):
        """GET /v1/agents/{agent_id}/keys/latest when version=None."""
        mock_transport.add_response(
            "GET", "/v1/agents/agent_test_001/keys/latest",
            status=200,
            body={
                "agent_id": "agent_test_001",
                "key_version": 3,
                "wrapped_dek_b64": "dGVzdA==",
                "created_at": "2026-02-15T00:00:00Z",
            },
        )

        result = await client.get_wrapped_dek(key_version=None)

        assert result is not None
        assert result["key_version"] == 3

    @pytest.mark.asyncio
    async def test_returns_none_on_404(self, client, mock_transport):
        """Returns None when no key exists."""
        # Default mock returns 404

        result = await client.get_wrapped_dek(key_version=1)
        assert result is None


class TestStorePayloadBlob:
    """Test store_payload_blob method."""

    @pytest.mark.asyncio
    async def test_sends_post_with_all_fields(self, client, mock_transport):
        """POST to /v1/payload with all required fields."""
        mock_transport.add_response(
            "POST", "/v1/payload",
            status=200,
            body={"stored": True, "payload_hash": "abc" + "0" * 61, "ciphertext_length": 100},
        )

        result = await client.store_payload_blob(
            payload_hash="abc" + "0" * 61,
            stamp_hash="def" + "0" * 61,
            ciphertext_b64="dGVzdA==",
            plaintext_length=84,
            key_version=2,
        )

        assert result["stored"] is True
        req = mock_transport.requests[0]
        body = json.loads(req.content)
        assert body["payload_hash"] == "abc" + "0" * 61
        assert body["stamp_hash"] == "def" + "0" * 61
        assert body["agent_id"] == "agent_test_001"
        assert body["ciphertext_b64"] == "dGVzdA=="
        assert body["plaintext_length"] == 84
        assert body["key_version"] == 2

    @pytest.mark.asyncio
    async def test_sends_bearer_auth(self, client, mock_transport):
        """Authorization header present on blob storage."""
        mock_transport.add_response(
            "POST", "/v1/payload",
            status=200,
            body={"stored": True, "payload_hash": "a" * 64, "ciphertext_length": 10},
        )

        await client.store_payload_blob(
            payload_hash="a" * 64,
            stamp_hash="b" * 64,
            ciphertext_b64="dGVzdA==",
            plaintext_length=4,
        )

        req = mock_transport.requests[0]
        assert req.headers["authorization"] == "Bearer lsk_test_key_abc123"


class TestGetPayloadBlob:
    """Test get_payload_blob method."""

    @pytest.mark.asyncio
    async def test_retrieves_by_hash(self, client, mock_transport):
        """GET /v1/payload/{hash}."""
        hash_val = "a" * 64
        mock_transport.add_response(
            "GET", f"/v1/payload/{hash_val}",
            status=200,
            body={
                "payload_hash": hash_val,
                "stamp_hash": "b" * 64,
                "agent_id": "agent_test_001",
                "ciphertext_b64": "dGVzdA==",
                "plaintext_length": 4,
                "key_version": 1,
                "created_at": "2026-02-15T00:00:00Z",
            },
        )

        result = await client.get_payload_blob(hash_val)

        assert result is not None
        assert result["payload_hash"] == hash_val
        assert result["key_version"] == 1

    @pytest.mark.asyncio
    async def test_returns_none_on_404(self, client, mock_transport):
        """Returns None when blob not found."""
        result = await client.get_payload_blob("nonexistent" + "0" * 53)
        assert result is None
