"""
Full integration tests for the privacy-creds SDK.

Tests the complete flow: issuer → wallet → proof → verifier.
"""

import copy
import tempfile
from pathlib import Path

import pytest

from privacy_creds import (
    Issuer,
    PlayerWallet,
    GameVerifier,
    FileStorage,
    CredentialError,
    WalletError,
)


@pytest.fixture
def tmp_storage(tmp_path):
    """Fresh storage directory for each test."""
    return FileStorage(tmp_path / "storage")


@pytest.fixture
def issuer(tmp_storage):
    """Issuer with keys generated."""
    iss = Issuer(storage=tmp_storage)
    iss.generate_keys()
    return iss


@pytest.fixture
def issuer_pubkey_pem(issuer):
    """Issuer public key as PEM bytes."""
    return issuer.get_public_key_pem()


@pytest.fixture
def verifier(issuer_pubkey_pem):
    """Game-side verifier."""
    return GameVerifier(issuer_pubkey_pem=issuer_pubkey_pem)


@pytest.fixture
def adult_credential(issuer):
    """Credential for an over-18 user."""
    return issuer.issue(user_id="u001", age=22, country="GB")


@pytest.fixture
def minor_credential(issuer):
    """Credential for an under-18 user."""
    return issuer.issue(user_id="u002", age=16, country="US")


# ---------------------------------------------------------------------------
# Issuer tests
# ---------------------------------------------------------------------------

class TestIssuer:
    def test_issue_valid_credential(self, issuer):
        cred = issuer.issue(user_id="u001", age=22, country="GB")
        assert cred.issuer == "privacy-creds-issuer"
        assert cred.subject_id == "u001"
        assert cred.claims["age_over_18"] is True
        assert cred.claims["age_over_21"] is True

    def test_issue_minor_credential(self, issuer):
        cred = issuer.issue(user_id="u002", age=16, country="US")
        assert cred.claims["age_over_13"] is True
        assert cred.claims["age_over_16"] is True
        assert cred.claims["age_over_18"] is False
        assert cred.claims["age_over_21"] is False

    def test_issue_invalid_age_negative(self, issuer):
        with pytest.raises(CredentialError):
            issuer.issue(user_id="u003", age=-1, country="GB")

    def test_issue_invalid_age_too_high(self, issuer):
        with pytest.raises(CredentialError):
            issuer.issue(user_id="u003", age=200, country="GB")

    def test_issue_invalid_country(self, issuer):
        with pytest.raises(CredentialError):
            issuer.issue(user_id="u003", age=25, country="")

    def test_issue_country_normalised_to_uppercase(self, issuer):
        cred = issuer.issue(user_id="u003", age=25, country="gb")
        assert cred.claims["country"] == "GB"


# ---------------------------------------------------------------------------
# Full flow tests
# ---------------------------------------------------------------------------

class TestFullFlow:
    def test_adult_allowed(self, tmp_storage, adult_credential, verifier):
        wallet = PlayerWallet(user_id="u001", storage_dir=tmp_storage.base_dir)
        wallet.setup(adult_credential.to_dict())
        assert wallet.is_setup

        challenge = verifier.create_challenge()
        proof = wallet.prove(challenge)
        result = verifier.verify(proof, required_age=18)

        assert result.allowed is True
        assert result.reason == "Verified"

    def test_minor_blocked(self, tmp_storage, minor_credential, verifier):
        wallet = PlayerWallet(user_id="u002", storage_dir=tmp_storage.base_dir)
        wallet.setup(minor_credential.to_dict())

        challenge = verifier.create_challenge()
        proof = wallet.prove(challenge)
        result = verifier.verify(proof, required_age=18)

        assert result.allowed is False
        assert "Age requirement" in result.reason

    def test_minor_allowed_for_age_13(self, tmp_storage, minor_credential, verifier):
        wallet = PlayerWallet(user_id="u002", storage_dir=tmp_storage.base_dir)
        wallet.setup(minor_credential.to_dict())

        challenge = verifier.create_challenge()
        proof = wallet.prove(challenge)
        result = verifier.verify(proof, required_age=13)

        assert result.allowed is True

    def test_tampered_challenge_rejected(self, tmp_storage, adult_credential, verifier):
        wallet = PlayerWallet(user_id="u001", storage_dir=tmp_storage.base_dir)
        wallet.setup(adult_credential.to_dict())

        challenge = verifier.create_challenge()
        proof = wallet.prove(challenge)
        # Swap to a different challenge after signing
        proof["challenge"] = verifier.create_challenge()

        result = verifier.verify(proof)
        assert result.allowed is False
        assert "does not own" in result.reason

    def test_tampered_credential_rejected(self, tmp_storage, adult_credential, verifier):
        wallet = PlayerWallet(user_id="u001", storage_dir=tmp_storage.base_dir)
        wallet.setup(adult_credential.to_dict())

        challenge = verifier.create_challenge()
        proof = wallet.prove(challenge)
        # Tamper with the credential claims — flip a value to invalidate signature
        proof["credential"]["claims"]["age_over_25"] = True
        proof["credential"]["claims"]["country"] = "XX"

        result = verifier.verify(proof)
        assert result.allowed is False
        assert "not from trusted issuer" in result.reason

    def test_missing_proof_field_rejected(self, verifier):
        result = verifier.verify({"challenge": "abc"})
        assert result.allowed is False
        assert "Missing field" in result.reason

    def test_wrong_issuer_key_rejected(self, tmp_storage, adult_credential):
        # Create a second issuer with different keys
        other_storage = FileStorage(tmp_storage.base_dir / "other_issuer")
        other_issuer = Issuer(storage=other_storage)
        other_issuer.generate_keys()

        # Verify with the wrong issuer's public key
        wrong_pubkey = other_issuer.get_public_key_pem()
        verifier = GameVerifier(issuer_pubkey_pem=wrong_pubkey)

        wallet = PlayerWallet(user_id="u001", storage_dir=tmp_storage.base_dir)
        wallet.setup(adult_credential.to_dict())

        challenge = verifier.create_challenge()
        proof = wallet.prove(challenge)
        result = verifier.verify(proof)

        assert result.allowed is False
        assert "not from trusted issuer" in result.reason

    def test_unsupported_age_threshold(self, tmp_storage, adult_credential, verifier):
        wallet = PlayerWallet(user_id="u001", storage_dir=tmp_storage.base_dir)
        wallet.setup(adult_credential.to_dict())

        challenge = verifier.create_challenge()
        proof = wallet.prove(challenge)
        result = verifier.verify(proof, required_age=30)

        assert result.allowed is False
        assert "Unsupported" in result.reason


# ---------------------------------------------------------------------------
# Wallet validation tests
# ---------------------------------------------------------------------------

class TestWalletValidation:
    def test_invalid_user_id_rejected(self):
        with pytest.raises(WalletError):
            PlayerWallet(user_id="user@bad!", storage_dir="/tmp/test")

    def test_empty_user_id_rejected(self):
        with pytest.raises(WalletError):
            PlayerWallet(user_id="", storage_dir="/tmp/test")

    def test_prove_without_setup_fails(self, tmp_storage):
        wallet = PlayerWallet(user_id="u999", storage_dir=tmp_storage.base_dir)
        with pytest.raises(WalletError, match="not set up"):
            wallet.prove("challenge")

    def test_prove_empty_challenge_fails(self, tmp_storage, adult_credential):
        wallet = PlayerWallet(user_id="u001", storage_dir=tmp_storage.base_dir)
        wallet.setup(adult_credential.to_dict())
        with pytest.raises(WalletError, match="non-empty"):
            wallet.prove("")

    def test_setup_bad_credential_rejected(self, tmp_storage):
        wallet = PlayerWallet(user_id="u001", storage_dir=tmp_storage.base_dir)
        with pytest.raises(WalletError, match="credential"):
            wallet.setup({"bad": "data"})


# ---------------------------------------------------------------------------
# Storage tests
# ---------------------------------------------------------------------------

class TestStorage:
    def test_path_traversal_blocked(self, tmp_storage):
        with pytest.raises(WalletError, match="Invalid filename"):
            tmp_storage.write_bytes("../../etc/passwd", b"hack")

    def test_secure_delete(self, tmp_storage):
        tmp_storage.write_bytes("secret.txt", b"sensitive data")
        assert tmp_storage.exists("secret.txt")
        tmp_storage.delete("secret.txt")
        assert not tmp_storage.exists("secret.txt")
