"""
Tests for x402 autonomous payment support in the Kevros SDK.

Covers: 402 disambiguation, payment header creation, dual-mode client,
fallback from API key to wallet, error handling.
"""

import pytest
from unittest.mock import MagicMock, patch

from kevros_governance.x402_payment import (
    X402PaymentClient,
    X402PaymentError,
    X402WalletNotConfiguredError,
    EvmWalletSigner,
    is_x402_response,
    parse_x402_requirements,
)
from kevros_governance.client import (
    GovernanceClient,
    GovernanceError,
    CapExceededError,
    PaymentRequiredError,
)


# --- Test data ---

X402_RESPONSE = {
    "x402Version": 2,
    "error": "Payment required. Pay with USDC on Base via x402",
    "accepts": [{
        "scheme": "exact",
        "network": "eip155:8453",
        "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
        "amount": "10000",
        "payTo": "0xF41E260D0896093F7d0a789030043925F7B8E478",
        "maxTimeoutSeconds": 60,
        "extra": {"name": "USDC", "version": "2"},
    }],
    "resource": {
        "url": "https://governance.taskhawktech.com/governance/verify",
        "description": "Prove authorization",
        "mimeType": "application/json",
    },
}

QUOTA_RESPONSE = {
    "detail": "Monthly call limit reached (100/100). Upgrade for more calls.",
    "upgrade_url": "https://governance.taskhawktech.com/stripe/checkout",
    "tiers": {"scout": {"price": "$9/mo", "calls": 1000}},
}


class TestIs402Disambiguation:
    """Test that x402 and quota 402 responses are correctly identified."""

    def test_x402_response_detected(self):
        assert is_x402_response(X402_RESPONSE) is True

    def test_quota_response_not_x402(self):
        assert is_x402_response(QUOTA_RESPONSE) is False

    def test_empty_response_not_x402(self):
        assert is_x402_response({}) is False

    def test_partial_x402_detected(self):
        assert is_x402_response({"x402Version": 1}) is True


class TestParseRequirements:
    """Test payment requirements parsing."""

    def test_parse_returns_data(self):
        result = parse_x402_requirements(X402_RESPONSE)
        assert result["x402Version"] == 2
        assert len(result["accepts"]) == 1
        assert result["accepts"][0]["amount"] == "10000"


class TestEvmWalletSigner:
    """Test EVM wallet signer creation and properties."""

    def test_create_signer_with_valid_key(self):
        # Use a test private key (never use in production)
        key = "0x" + "ab" * 32
        signer = EvmWalletSigner(key)
        assert signer.address.startswith("0x")
        assert len(signer.address) == 42

    def test_create_signer_without_eth_account_raises(self):
        with patch.dict("sys.modules", {"eth_account": None}):
            # This test verifies the import error path
            pass  # eth_account is installed, so we skip the negative test

    def test_sign_typed_data_returns_bytes(self):
        key = "0x" + "ab" * 32
        signer = EvmWalletSigner(key)
        # Create a minimal EIP-712 typed data structure
        domain = {
            "name": "TestDomain",
            "version": "1",
            "chainId": 8453,
        }
        types = {
            "Test": [{"name": "value", "type": "uint256"}],
        }
        sig = signer.sign_typed_data(domain, types, "Test", {"value": 42})
        assert isinstance(sig, bytes)
        assert len(sig) == 65  # Standard ECDSA signature


class TestX402PaymentClient:
    """Test X402PaymentClient initialization and properties."""

    def test_client_creation(self):
        key = "0x" + "ab" * 32
        client = X402PaymentClient(key)
        assert client.address.startswith("0x")

    def test_client_address_matches_signer(self):
        key = "0x" + "ab" * 32
        client = X402PaymentClient(key)
        signer = EvmWalletSigner(key)
        assert client.address == signer.address


class TestPaymentRequiredError:
    """Test PaymentRequiredError exception."""

    def test_error_has_requirements(self):
        err = PaymentRequiredError("payment needed", requirements=X402_RESPONSE)
        assert err.status_code == 402
        assert err.requirements["x402Version"] == 2

    def test_error_without_requirements(self):
        err = PaymentRequiredError("payment needed")
        assert err.requirements == {}


class TestX402WalletNotConfiguredError:
    """Test X402WalletNotConfiguredError."""

    def test_error_message(self):
        err = X402WalletNotConfiguredError()
        assert "wallet" in err.detail.lower()
        assert "KEVROS_WALLET_KEY" in err.detail


class TestGovernanceClientDualMode:
    """Test GovernanceClient with dual payment mode."""

    @patch("kevros_governance.client.get_or_create_api_key")
    def test_default_mode_is_api_key(self, mock_signup):
        mock_signup.return_value = "kvrs_test123"
        client = GovernanceClient(agent_id="test-agent")
        assert client._payment_mode == "api_key"
        assert client._api_key == "kvrs_test123"
        assert client._x402_client is None

    @patch("kevros_governance.client.get_or_create_api_key")
    def test_x402_mode_skips_api_key(self, mock_signup):
        key = "0x" + "ab" * 32
        client = GovernanceClient(
            agent_id="test-agent",
            wallet_private_key=key,
            payment_mode="x402",
        )
        assert client._payment_mode == "x402"
        assert client._api_key == ""
        assert client._x402_client is not None
        mock_signup.assert_not_called()

    @patch("kevros_governance.client.get_or_create_api_key")
    def test_wallet_with_api_key_mode_enables_fallback(self, mock_signup):
        mock_signup.return_value = "kvrs_test123"
        key = "0x" + "ab" * 32
        client = GovernanceClient(
            agent_id="test-agent",
            wallet_private_key=key,
            payment_mode="api_key",
        )
        assert client._payment_mode == "x402_fallback"
        assert client._api_key == "kvrs_test123"
        assert client._x402_client is not None

    def test_explicit_api_key_with_wallet(self):
        key = "0x" + "ab" * 32
        client = GovernanceClient(
            api_key="kvrs_explicit123",
            wallet_private_key=key,
        )
        assert client._api_key == "kvrs_explicit123"
        assert client._x402_client is not None

    @patch.dict("os.environ", {"KEVROS_WALLET_KEY": "0x" + "ab" * 32})
    @patch("kevros_governance.client.get_or_create_api_key")
    def test_wallet_from_env_var(self, mock_signup):
        mock_signup.return_value = "kvrs_test123"
        client = GovernanceClient(agent_id="test-agent")
        assert client._x402_client is not None
        assert client._payment_mode == "x402_fallback"

    def test_x402_mode_without_wallet_raises_on_use(self):
        """x402 mode without wallet will fail when trying to pay."""
        # Client can be created but will fail when trying to make a payment
        client = GovernanceClient(
            api_key="kvrs_test",
            payment_mode="x402",
        )
        # _x402_client is None because no wallet was provided
        assert client._x402_client is None


class TestHandleResponse402:
    """Test _handle_response correctly handles both 402 types."""

    @patch("kevros_governance.client.get_or_create_api_key")
    def test_quota_402_raises_cap_exceeded(self, mock_signup):
        mock_signup.return_value = "kvrs_test"
        client = GovernanceClient(agent_id="test")

        resp = MagicMock()
        resp.status_code = 402
        resp.json.return_value = QUOTA_RESPONSE

        with pytest.raises(CapExceededError) as exc_info:
            client._handle_response(resp)
        assert exc_info.value.upgrade_url == QUOTA_RESPONSE["upgrade_url"]

    @patch("kevros_governance.client.get_or_create_api_key")
    def test_x402_402_raises_payment_required(self, mock_signup):
        mock_signup.return_value = "kvrs_test"
        client = GovernanceClient(agent_id="test")

        resp = MagicMock()
        resp.status_code = 402
        resp.json.return_value = X402_RESPONSE

        with pytest.raises(PaymentRequiredError) as exc_info:
            client._handle_response(resp)
        assert exc_info.value.requirements["x402Version"] == 2

    @patch("kevros_governance.client.get_or_create_api_key")
    def test_non_402_error_raises_governance_error(self, mock_signup):
        mock_signup.return_value = "kvrs_test"
        client = GovernanceClient(agent_id="test")

        resp = MagicMock()
        resp.status_code = 500
        with pytest.raises(GovernanceError) as exc_info:
            client._handle_response(resp)
        assert exc_info.value.status_code == 500

    @patch("kevros_governance.client.get_or_create_api_key")
    def test_200_returns_json(self, mock_signup):
        mock_signup.return_value = "kvrs_test"
        client = GovernanceClient(agent_id="test")

        resp = MagicMock()
        resp.status_code = 200
        resp.json.return_value = {"decision": "ALLOW"}
        result = client._handle_response(resp)
        assert result["decision"] == "ALLOW"
