"""Unit tests for the 0byte SDK client (mocked HTTP)."""

import base64
import json
import pytest
import httpx
from unittest.mock import patch, MagicMock

from zerobyte import Client, Proof, VerificationResult
from zerobyte.exceptions import AuthenticationError, ProofError, SDKError, VerificationError


SAMPLE_IMAGE = b"\x89PNG\r\n\x1a\n" + b"\x00" * 100  # minimal bytes

PROOF_RESPONSE = {
    "id": "0b_test-proof-id",
    "fingerprint": "f0e1d2c3b4a59687",
    "provider": "test-provider",
    "model": "test-model",
    "content_type": "image/png",
    "timestamp": "2026-03-12T00:00:00Z",
    "signature": "dGVzdC1zaWduYXR1cmU=",
    "signing_key_id": "abc123",
    "verify_url": "https://0byte.tech/proof/0b_test-proof-id",
    "metadata": {"test": True},
}


def mock_response(status_code, data):
    resp = MagicMock(spec=httpx.Response)
    resp.status_code = status_code
    resp.json.return_value = data
    resp.text = json.dumps(data)
    return resp


class TestProof:
    def test_from_dict(self):
        proof = Proof(PROOF_RESPONSE)
        assert proof.id == "0b_test-proof-id"
        assert proof.fingerprint == "f0e1d2c3b4a59687"
        assert proof.provider == "test-provider"
        assert proof.model == "test-model"
        assert proof.content_type == "image/png"
        assert proof.signature == "dGVzdC1zaWduYXR1cmU="
        assert proof.verify_url == "https://0byte.tech/proof/0b_test-proof-id"
        assert proof.metadata == {"test": True}

    def test_repr(self):
        proof = Proof(PROOF_RESPONSE)
        assert "test-provider" in repr(proof)
        assert "test-model" in repr(proof)


class TestVerificationResult:
    def test_matched(self):
        data = {"matched": True, "confidence": 0.95, "proof": PROOF_RESPONSE}
        result = VerificationResult(data)
        assert result.matched is True
        assert result.confidence == 0.95
        assert result.proof.id == "0b_test-proof-id"
        assert "matched=True" in repr(result)

    def test_not_matched(self):
        data = {"matched": False, "confidence": 0.0, "proof": None}
        result = VerificationResult(data)
        assert result.matched is False
        assert result.proof is None
        assert "matched=False" in repr(result)


class TestClientStamp:
    def test_stamp_success(self):
        client = Client(api_key="0b_key_test")
        client._http = MagicMock()
        client._http.post.return_value = mock_response(200, PROOF_RESPONSE)

        proof = client.stamp(
            content=SAMPLE_IMAGE,
            content_type="image/png",
            provider="test-provider",
            model="test-model",
        )

        assert proof.id == "0b_test-proof-id"
        assert proof.provider == "test-provider"

        # Verify the request was made correctly
        call_args = client._http.post.call_args
        assert "/v1/stamp" in call_args[0][0]
        payload = call_args[1]["json"]
        assert payload["content"] == base64.b64encode(SAMPLE_IMAGE).decode()
        assert payload["content_type"] == "image/png"
        assert payload["provider"] == "test-provider"
        assert payload["model"] == "test-model"
        headers = call_args[1]["headers"]
        assert headers["Authorization"] == "Bearer 0b_key_test"

    def test_stamp_with_metadata(self):
        client = Client(api_key="0b_key_test")
        client._http = MagicMock()
        client._http.post.return_value = mock_response(200, PROOF_RESPONSE)

        client.stamp(
            content=SAMPLE_IMAGE,
            content_type="image/png",
            provider="test",
            model="test",
            metadata={"prompt_hash": "abc"},
        )

        payload = client._http.post.call_args[1]["json"]
        assert payload["metadata"] == {"prompt_hash": "abc"}

    def test_stamp_no_api_key(self):
        client = Client()
        with pytest.raises(AuthenticationError, match="API key required"):
            client.stamp(SAMPLE_IMAGE, "image/png", "test", "test")

    def test_stamp_invalid_api_key(self):
        client = Client(api_key="bad_key")
        client._http = MagicMock()
        client._http.post.return_value = mock_response(401, {"error": "unauthorized"})

        with pytest.raises(AuthenticationError, match="Invalid API key"):
            client.stamp(SAMPLE_IMAGE, "image/png", "test", "test")

    def test_stamp_server_error(self):
        client = Client(api_key="0b_key_test")
        client._http = MagicMock()
        client._http.post.return_value = mock_response(500, {"error": "internal"})

        with pytest.raises(ProofError, match="Stamp failed"):
            client.stamp(SAMPLE_IMAGE, "image/png", "test", "test")


class TestClientVerify:
    def test_verify_matched(self):
        client = Client()
        client._http = MagicMock()
        client._http.post.return_value = mock_response(
            200, {"matched": True, "confidence": 0.95, "proof": PROOF_RESPONSE}
        )

        result = client.verify(SAMPLE_IMAGE, "image/png")

        assert result.matched is True
        assert result.confidence == 0.95
        assert result.proof.id == "0b_test-proof-id"

    def test_verify_no_match(self):
        client = Client()
        client._http = MagicMock()
        client._http.post.return_value = mock_response(
            200, {"matched": False, "confidence": 0.0, "proof": None}
        )

        result = client.verify(SAMPLE_IMAGE)

        assert result.matched is False
        assert result.proof is None

    def test_verify_no_auth_needed(self):
        """Verify should work without an API key."""
        client = Client()  # no api_key
        client._http = MagicMock()
        client._http.post.return_value = mock_response(
            200, {"matched": False, "confidence": 0.0, "proof": None}
        )

        result = client.verify(SAMPLE_IMAGE)
        assert result.matched is False

        # Should not send auth header
        call_args = client._http.post.call_args
        assert "headers" not in call_args[1] or "Authorization" not in call_args[1].get("headers", {})

    def test_verify_server_error(self):
        client = Client()
        client._http = MagicMock()
        client._http.post.return_value = mock_response(500, {"error": "internal"})

        with pytest.raises(VerificationError, match="Verification failed"):
            client.verify(SAMPLE_IMAGE)


class TestClientGetProof:
    def test_get_proof_success(self):
        client = Client()
        client._http = MagicMock()
        client._http.get.return_value = mock_response(200, PROOF_RESPONSE)

        proof = client.get_proof("0b_test-proof-id")

        assert proof.id == "0b_test-proof-id"
        call_args = client._http.get.call_args
        assert "/v1/proofs/0b_test-proof-id" in call_args[0][0]

    def test_get_proof_not_found(self):
        client = Client()
        client._http = MagicMock()
        client._http.get.return_value = mock_response(404, {"error": "not found"})

        with pytest.raises(ProofError, match="Proof not found"):
            client.get_proof("0b_nonexistent")


class TestClientKeyManagement:
    def test_create_key(self):
        client = Client(api_key="0b_key_test")
        client._http = MagicMock()
        client._http.post.return_value = mock_response(201, {
            "id": "new-key-id",
            "name": "production",
            "created_at": "2026-03-12T00:00:00",
            "key": "0b_key_newkey123",
        })

        result = client.create_key("production")

        assert result["id"] == "new-key-id"
        assert result["key"] == "0b_key_newkey123"
        call_args = client._http.post.call_args
        assert "/v1/keys" in call_args[0][0]
        assert call_args[1]["headers"]["Authorization"] == "Bearer 0b_key_test"

    def test_list_keys(self):
        client = Client(api_key="0b_key_test")
        client._http = MagicMock()
        client._http.get.return_value = mock_response(200, [
            {"id": "key-1", "name": "default", "created_at": "2026-03-11"},
            {"id": "key-2", "name": "prod", "created_at": "2026-03-12"},
        ])

        keys = client.list_keys()

        assert len(keys) == 2
        assert keys[0]["name"] == "default"
        assert keys[1]["name"] == "prod"

    def test_revoke_key(self):
        client = Client(api_key="0b_key_test")
        client._http = MagicMock()
        client._http.delete.return_value = mock_response(200, {"deleted": True, "id": "key-2"})

        result = client.revoke_key("key-2")

        assert result is True
        call_args = client._http.delete.call_args
        assert "/v1/keys/key-2" in call_args[0][0]

    def test_key_ops_require_auth(self):
        client = Client()  # no api_key
        with pytest.raises(AuthenticationError):
            client.create_key("test")
        with pytest.raises(AuthenticationError):
            client.list_keys()
        with pytest.raises(AuthenticationError):
            client.revoke_key("some-id")


class TestClientLifecycle:
    def test_context_manager(self):
        with Client(api_key="test") as client:
            assert client.api_key == "test"

    def test_custom_endpoint(self):
        client = Client(endpoint="https://custom.api.com/")
        assert client.endpoint == "https://custom.api.com"  # trailing slash stripped

    def test_default_endpoint(self):
        client = Client()
        assert client.endpoint == "https://api.0byte.tech"
