"""Integration tests against the real CertiSigma API.

Run with::

    CERTISIGMA_API_KEY=cs_demo_xxx pytest -m integration -v

These tests call the REAL CertiSigma API.  Recommend using a ``cs_demo_``
sandbox key to avoid polluting production data.

- Public endpoint tests run without a key.
- Authenticated tests skip if ``CERTISIGMA_API_KEY`` is not set.
- Attestations are immutable; tags/metadata/shares are cleaned up.
- Each test uses unique hashes (timestamp-salted) to avoid collisions.
"""

from __future__ import annotations

import os
import time
import uuid
import tempfile
from pathlib import Path

import pytest

from certisigma import (
    CertiSigmaClient,
    hash_file,
    hash_bytes,
    CertiSigmaError,
    AuthenticationError,
)

pytestmark = pytest.mark.integration

API_KEY_ENV = "CERTISIGMA_API_KEY"


def _unique_content() -> bytes:
    """Generate unique content to avoid hash collisions across runs."""
    return f"census-integration-{uuid.uuid4().hex}-{time.time()}".encode()


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture(scope="module")
def api_key():
    key = os.environ.get(API_KEY_ENV)
    if not key:
        pytest.skip(f"{API_KEY_ENV} not set")
    return key


@pytest.fixture(scope="module")
def client(api_key):
    c = CertiSigmaClient(api_key=api_key)
    yield c
    c.close()


@pytest.fixture(scope="module")
def public_client():
    """Client without API key — public endpoints only."""
    c = CertiSigmaClient()
    yield c
    c.close()


@pytest.fixture(scope="module")
def attested_hash(client):
    """Attest a unique hash and return (hash_hex, attestation_id, raw_id).

    raw_id preserves the original type from the API (int or str).
    """
    content = _unique_content()
    h = hash_bytes(content)
    result = client.attest(h, source="census-integration-test")
    return h, str(result.id), result.id


# ---------------------------------------------------------------------------
# Public endpoints (no API key)
# ---------------------------------------------------------------------------

class TestPublicEndpoints:

    def test_health(self, public_client):
        result = public_client.health()
        assert result.get("status") == "ok"

    def test_verify_unknown_hash(self, public_client):
        unknown = hash_bytes(b"this-will-never-match-" + uuid.uuid4().bytes)
        result = public_client.verify(unknown)
        assert result.exists is False

    def test_batch_verify_unknown(self, public_client):
        hashes = [hash_bytes(f"no-match-{i}-{uuid.uuid4().hex}".encode()) for i in range(3)]
        result = public_client.batch_verify(hashes)
        assert result.count == 3
        assert result.found == 0
        assert result.not_found == 3


# ---------------------------------------------------------------------------
# Attestation lifecycle
# ---------------------------------------------------------------------------

class TestAttestationLifecycle:

    def test_attest_and_verify(self, client):
        content = _unique_content()
        h = hash_bytes(content)
        att = client.attest(h, source="census-integration-test")
        assert att.id
        assert att.hash_hex == h

        verify = client.verify(h)
        assert verify.exists is True
        assert verify.hash_hex == h

    def test_batch_attest_and_verify(self, client):
        hashes = [hash_bytes(_unique_content()) for _ in range(3)]
        result = client.batch_attest(hashes, source="census-integration-batch")
        assert result.count == 3

        verify = client.batch_verify(hashes)
        assert verify.found == 3

    def test_attest_file(self, client):
        path = None
        try:
            with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
                f.write(_unique_content())
                path = f.name
            result = client.attest_file(path, source="census-integration-file")
            assert result.id
            assert result.hash_hex == hash_file(path)
        finally:
            if path and os.path.exists(path):
                os.unlink(path)

    def test_get_evidence(self, client, attested_hash):
        h, att_id, _ = attested_hash
        evidence = client.get_evidence(att_id)
        assert evidence.hash_hex == h
        assert evidence.level in ("T0", "T1", "T2")

    def test_status(self, client, attested_hash):
        _, att_id, _ = attested_hash
        status = client.status(att_id)
        assert status.level in ("T0", "T1", "T2")


# ---------------------------------------------------------------------------
# Metadata lifecycle
# ---------------------------------------------------------------------------

class TestMetadataLifecycle:

    def test_update_get_delete(self, client, attested_hash):
        _, att_id, _ = attested_hash
        extra = {"census_test": True, "run_id": uuid.uuid4().hex}
        client.update_metadata(att_id, source="integration-meta", extra_data=extra)

        meta = client.get_metadata(att_id)
        assert meta.source == "integration-meta"

        client.delete_metadata(att_id)


# ---------------------------------------------------------------------------
# Tagging lifecycle
# ---------------------------------------------------------------------------

class TestTaggingLifecycle:

    def test_put_get_delete(self, client, attested_hash):
        _, att_id, _ = attested_hash
        tag_key = f"census_test_{uuid.uuid4().hex[:8]}"
        tags = [{"key": tag_key, "value": "integration"}]

        client.put_tags(att_id, tags)
        result = client.get_tags(att_id)
        found = [t for t in result if t.key == tag_key]
        assert len(found) >= 1
        assert found[0].value == "integration"

        client.delete_tag(att_id, tag_key)


# ---------------------------------------------------------------------------
# Sharing lifecycle
# ---------------------------------------------------------------------------

class TestSharingLifecycle:

    def test_create_list_revoke(self, client, attested_hash):
        _, att_id, raw_id = attested_hash
        att_int = int(raw_id) if not isinstance(raw_id, int) else raw_id
        token = client.create_share_token(
            [att_int],
            expires_in=3600,
            recipient_label="census-test",
        )
        assert token.share_token

        tokens = client.list_share_tokens()
        assert any(t.token_id == token.token_id for t in tokens)

        client.revoke_share_token(token.token_id)


# ---------------------------------------------------------------------------
# Derived lists
# ---------------------------------------------------------------------------

class TestDerivedLists:

    def test_create_match_revoke(self, client, attested_hash):
        h, _, _ = attested_hash
        result = client.create_derived_list(
            hashes=[h], label="census-integration-test"
        )
        assert result.list_id
        assert result.list_key

        match = client.match_derived_list(result.list_id, result.list_key, [h])
        assert match.matched >= 1

        client.revoke_derived_list(result.list_id)

    def test_derived_list_signature(self, client, attested_hash):
        h, _, _ = attested_hash
        dl = client.create_derived_list(
            hashes=[h], label="census-sig-test"
        )
        try:
            sig = client.get_derived_list_signature(dl.list_id)
            assert sig.signature
        finally:
            client.revoke_derived_list(dl.list_id)


# ---------------------------------------------------------------------------
# Error handling
# ---------------------------------------------------------------------------

class TestErrorHandling:

    def test_auth_error_invalid_key(self):
        bad_client = CertiSigmaClient(api_key="cs_invalid_key_12345")
        try:
            with pytest.raises(AuthenticationError):
                bad_client.attest(hash_bytes(b"auth-test"), source="should-fail")
        finally:
            bad_client.close()

    def test_not_found_error(self, client):
        with pytest.raises(CertiSigmaError):
            client.get_evidence("999999999")
