"""Tests for the Vaultfire AgentKit action provider.

These tests verify:
  1. Provider instantiation and action discovery
  2. Network support checks
  3. Schema validation for all actions
  4. Contract address integrity (all checksummed, all present per chain)
  5. No secrets, keys, or sensitive data in the codebase
"""

import re
import unittest
from unittest.mock import MagicMock, patch

from web3 import Web3

from vaultfire_agentkit.constants import (
    ACCOUNTABILITY_BONDS_ADDRESSES,
    AGENT_TYPE_MAP,
    CHAIN_IDS,
    EXPLORER_URLS,
    IDENTITY_REGISTRY_ABI,
    IDENTITY_REGISTRY_ADDRESSES,
    PARTNERSHIP_BONDS_ADDRESSES,
    REPUTATION_REGISTRY_ADDRESSES,
    SUPPORTED_NETWORKS,
    VNS_ADDRESSES,
)
from vaultfire_agentkit.schemas import (
    CreateAccountabilityBondSchema,
    CreatePartnershipBondSchema,
    GetBondSchema,
    GetReputationSchema,
    GetTrustProfileSchema,
    RegisterAgentSchema,
    ResolveVnsSchema,
)
from vaultfire_agentkit.vaultfire_action_provider import (
    VaultfireActionProvider,
    vaultfire_action_provider,
)


class TestProviderInit(unittest.TestCase):
    """Test provider creation and action discovery."""

    def test_factory_returns_provider(self):
        provider = vaultfire_action_provider()
        self.assertIsInstance(provider, VaultfireActionProvider)

    def test_provider_name(self):
        provider = VaultfireActionProvider()
        self.assertEqual(provider.name, "vaultfire")

    def test_action_count(self):
        """Provider should expose exactly 7 actions."""
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        actions = provider.get_actions(mock_wallet)
        self.assertEqual(len(actions), 7)

    def test_action_names(self):
        """All expected action names must be present."""
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        actions = provider.get_actions(mock_wallet)
        names = {a.name for a in actions}
        expected = {
            "VaultfireActionProvider_register_agent",
            "VaultfireActionProvider_create_accountability_bond",
            "VaultfireActionProvider_create_partnership_bond",
            "VaultfireActionProvider_get_trust_profile",
            "VaultfireActionProvider_get_reputation",
            "VaultfireActionProvider_resolve_vns",
            "VaultfireActionProvider_get_bond",
        }
        self.assertEqual(names, expected)


class TestNetworkSupport(unittest.TestCase):
    """Test that supports_network correctly filters."""

    def test_base_mainnet_supported(self):
        from coinbase_agentkit.network import Network

        provider = VaultfireActionProvider()
        net = Network(protocol_family="evm", network_id="base-mainnet")
        self.assertTrue(provider.supports_network(net))

    def test_arbitrum_mainnet_supported(self):
        from coinbase_agentkit.network import Network

        provider = VaultfireActionProvider()
        net = Network(protocol_family="evm", network_id="arbitrum-mainnet")
        self.assertTrue(provider.supports_network(net))

    def test_ethereum_not_supported(self):
        from coinbase_agentkit.network import Network

        provider = VaultfireActionProvider()
        net = Network(protocol_family="evm", network_id="ethereum-mainnet")
        self.assertFalse(provider.supports_network(net))

    def test_solana_not_supported(self):
        from coinbase_agentkit.network import Network

        provider = VaultfireActionProvider()
        net = Network(protocol_family="solana", network_id="solana-mainnet")
        self.assertFalse(provider.supports_network(net))


class TestSchemaValidation(unittest.TestCase):
    """Test that all Pydantic schemas validate correctly."""

    def test_register_agent_defaults(self):
        s = RegisterAgentSchema(name="test-agent")
        self.assertEqual(s.agent_type, "autonomous")

    def test_register_agent_all_types(self):
        for t in ["autonomous", "assistant", "specialized", "multi_agent"]:
            s = RegisterAgentSchema(name="test", agent_type=t)
            self.assertEqual(s.agent_type, t)

    def test_accountability_bond_schema(self):
        s = CreateAccountabilityBondSchema(amount="0.01")
        self.assertEqual(s.amount, "0.01")

    def test_partnership_bond_schema(self):
        s = CreatePartnershipBondSchema(
            partner_address="0x" + "a" * 40, amount="0.05"
        )
        self.assertEqual(s.amount, "0.05")

    def test_trust_profile_schema(self):
        s = GetTrustProfileSchema(agent_address="0x" + "b" * 40)
        self.assertIn("0x", s.agent_address)

    def test_reputation_schema(self):
        s = GetReputationSchema(agent_address="0x" + "c" * 40)
        self.assertIn("0x", s.agent_address)

    def test_resolve_vns_schema(self):
        s = ResolveVnsSchema(query="my-agent.vns")
        self.assertEqual(s.query, "my-agent.vns")

    def test_get_bond_schema(self):
        s = GetBondSchema(bond_id=1, bond_type="accountability")
        self.assertEqual(s.bond_id, 1)


class TestContractAddresses(unittest.TestCase):
    """Verify all contract addresses are valid checksummed Ethereum addresses."""

    def _check_address_dict(self, addresses: dict[str, str], name: str):
        for network, addr in addresses.items():
            with self.subTest(contract=name, network=network):
                self.assertTrue(
                    Web3.is_checksum_address(addr),
                    f"{name} address on {network} is not checksummed: {addr}",
                )

    def test_identity_addresses(self):
        self._check_address_dict(IDENTITY_REGISTRY_ADDRESSES, "IdentityRegistry")

    def test_partnership_addresses(self):
        self._check_address_dict(PARTNERSHIP_BONDS_ADDRESSES, "PartnershipBonds")

    def test_accountability_addresses(self):
        self._check_address_dict(ACCOUNTABILITY_BONDS_ADDRESSES, "AccountabilityBonds")

    def test_reputation_addresses(self):
        self._check_address_dict(REPUTATION_REGISTRY_ADDRESSES, "ReputationRegistry")

    def test_vns_addresses(self):
        self._check_address_dict(VNS_ADDRESSES, "VNS")

    def test_all_mainnet_chains_have_identity(self):
        for net in ["base-mainnet", "arbitrum-mainnet", "polygon-mainnet", "avalanche-mainnet"]:
            self.assertIn(net, IDENTITY_REGISTRY_ADDRESSES, f"Missing {net}")

    def test_all_mainnet_chains_have_bonds(self):
        for net in ["base-mainnet", "arbitrum-mainnet", "polygon-mainnet", "avalanche-mainnet"]:
            self.assertIn(net, PARTNERSHIP_BONDS_ADDRESSES, f"Missing partnership {net}")
            self.assertIn(net, ACCOUNTABILITY_BONDS_ADDRESSES, f"Missing accountability {net}")


class TestNoSecretLeaks(unittest.TestCase):
    """CRITICAL: Verify no private keys, API tokens, or secrets in source files."""

    SENSITIVE_PATTERNS = [
        r"PRIVATE_KEY\s*=",           # Hardcoded key assignment
        r"SECRET\s*=\s*['\"][^'\"]+",  # Hardcoded secret assignment
        r"Bearer\s+[A-Za-z0-9_-]{20,}", # Hardcoded bearer token
    ]

    def test_no_secrets_in_source(self):
        """Scan every .py file in the package for sensitive patterns (excluding test files)."""
        import os

        root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        for dirpath, _, filenames in os.walk(root):
            for fname in filenames:
                if fname.endswith(".py") and not fname.startswith("test_"):
                    filepath = os.path.join(dirpath, fname)
                    with open(filepath) as f:
                        content = f.read()
                    for pattern in self.SENSITIVE_PATTERNS:
                        with self.subTest(file=fname, pattern=pattern):
                            self.assertIsNone(
                                re.search(pattern, content, re.IGNORECASE),
                                f"SECURITY: Found sensitive pattern '{pattern}' in {fname}",
                            )

    def test_no_secrets_in_constants(self):
        """Extra check: constants.py should not contain private key hex strings."""
        import os

        constants_path = os.path.join(
            os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
            "constants.py",
        )
        with open(constants_path) as f:
            content = f.read()

        # Should NOT contain 64-char hex strings (private key length)
        matches = re.findall(r'(?<![0-9a-fA-F])[0-9a-fA-F]{64}(?![0-9a-fA-F])', content)
        self.assertEqual(len(matches), 0, f"Found potential private key hex in constants.py: {matches}")

    def test_no_secrets_in_readme(self):
        """README should not contain private key hex strings."""
        import os

        readme_path = os.path.join(
            os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
            "README.md",
        )
        if os.path.exists(readme_path):
            with open(readme_path) as f:
                content = f.read()
            # No 64-char hex strings
            matches = re.findall(r'(?<![0-9a-fA-F])[0-9a-fA-F]{64}(?![0-9a-fA-F])', content)
            self.assertEqual(len(matches), 0, f"Found potential private key hex in README.md")


class TestAgentTypeMapping(unittest.TestCase):
    """Verify agent type enum mapping is correct."""

    def test_all_types_mapped(self):
        expected = {"autonomous": 0, "assistant": 1, "specialized": 2, "multi_agent": 3}
        self.assertEqual(AGENT_TYPE_MAP, expected)


class TestRegisterAgentAction(unittest.TestCase):
    """Test register_agent with mocked wallet provider."""

    def test_register_on_unsupported_network(self):
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        mock_wallet.get_network.return_value = MagicMock(network_id="ethereum-mainnet")
        mock_wallet.get_address.return_value = "0x" + "a" * 40

        result = provider.register_agent(
            mock_wallet, {"name": "test", "agent_type": "autonomous"}
        )
        self.assertIn("Error", result)

    def test_register_already_registered(self):
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        mock_wallet.get_network.return_value = MagicMock(network_id="base-mainnet")
        mock_wallet.get_address.return_value = "0x" + "a" * 40
        mock_wallet.read_contract.return_value = True  # Already registered

        result = provider.register_agent(
            mock_wallet, {"name": "test", "agent_type": "autonomous"}
        )
        self.assertIn("already registered", result)

    def test_register_success(self):
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        mock_wallet.get_network.return_value = MagicMock(network_id="base-mainnet")
        mock_wallet.get_address.return_value = "0x" + "a" * 40
        mock_wallet.read_contract.return_value = False  # Not registered
        mock_wallet.send_transaction.return_value = "0x" + "f" * 64
        mock_wallet.wait_for_transaction_receipt.return_value = {"status": 1}

        result = provider.register_agent(
            mock_wallet, {"name": "my-agent", "agent_type": "autonomous"}
        )
        self.assertIn("Successfully registered", result)
        self.assertIn("my-agent", result)
        mock_wallet.send_transaction.assert_called_once()


class TestBondActions(unittest.TestCase):
    """Test bond creation actions with mocked wallet."""

    def test_accountability_bond_unsupported_network(self):
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        mock_wallet.get_network.return_value = MagicMock(network_id="ethereum-mainnet")

        result = provider.create_accountability_bond(mock_wallet, {"amount": "0.01"})
        self.assertIn("Error", result)

    def test_accountability_bond_success(self):
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        mock_wallet.get_network.return_value = MagicMock(network_id="base-mainnet")
        mock_wallet.send_transaction.return_value = "0x" + "e" * 64
        mock_wallet.wait_for_transaction_receipt.return_value = {"status": 1}

        result = provider.create_accountability_bond(mock_wallet, {"amount": "0.01"})
        self.assertIn("Successfully created", result)
        self.assertIn("0.01", result)

        call_args = mock_wallet.send_transaction.call_args[0][0]
        self.assertEqual(call_args["value"], Web3.to_wei("0.01", "ether"))

    def test_partnership_bond_success(self):
        provider = VaultfireActionProvider()
        mock_wallet = MagicMock()
        mock_wallet.get_network.return_value = MagicMock(network_id="base-mainnet")
        mock_wallet.send_transaction.return_value = "0x" + "d" * 64
        mock_wallet.wait_for_transaction_receipt.return_value = {"status": 1}

        result = provider.create_partnership_bond(
            mock_wallet,
            {"partner_address": "0x" + "b" * 40, "amount": "0.05"},
        )
        self.assertIn("Successfully created", result)
        self.assertIn("Partner", result)


if __name__ == "__main__":
    unittest.main()
