"""Tests for AgentWallet – payment logic without real blockchain."""

from swarmpay.wallet import (
    AgentWallet, PaymentResult,
    _to_atomic_units, _encode_erc20_approve, _encode_erc20_transfer,
)
from swarmpay.fee_engine import FeeEngine, FeeConfig
from swarmpay.constants import USDC_CONTRACTS, ERC20_APPROVE_SELECTOR, ERC20_TRANSFER_SELECTOR


# ── Unit Conversion Tests ────────────────────────────────────

def test_usdc_to_atomic_units():
    """USDC has 6 decimals: $1.00 = 1_000_000"""
    assert _to_atomic_units(1.0, 6) == 1_000_000
    assert _to_atomic_units(0.50, 6) == 500_000
    assert _to_atomic_units(0.01, 6) == 10_000
    assert _to_atomic_units(0.001, 6) == 1_000


def test_eth_to_atomic_units():
    """ETH has 18 decimals: 1 ETH = 10^18 wei"""
    assert _to_atomic_units(1.0, 18) == 10**18
    assert _to_atomic_units(0.001, 18) == 10**15


def test_atomic_units_precision():
    """Decimal precision: no floating point errors."""
    # 0.1 + 0.2 should be exactly 0.3 in atomic units
    assert _to_atomic_units(0.1, 6) + _to_atomic_units(0.2, 6) == _to_atomic_units(0.3, 6)


# ── ERC-20 Encoding Tests ───────────────────────────────────

def test_erc20_transfer_encoding():
    """ERC-20 transfer calldata should start with 0xa9059cbb."""
    calldata = _encode_erc20_transfer("0x1234567890abcdef1234567890abcdef12345678", 1_000_000)
    assert calldata.startswith(ERC20_TRANSFER_SELECTOR)
    assert len(calldata) == 10 + 64 + 64  # selector(10) + address(64) + value(64)


def test_erc20_transfer_contains_recipient():
    recipient = "0xAbCdEf1234567890AbCdEf1234567890AbCdEf12"
    calldata = _encode_erc20_transfer(recipient, 500_000)
    # Address should be in the calldata (lowercase, without 0x, zero-padded)
    assert "abcdef1234567890abcdef1234567890abcdef12" in calldata.lower()


def test_erc20_approve_encoding():
    calldata = _encode_erc20_approve("0x1234567890abcdef1234567890abcdef12345678", 1_000_000)
    assert calldata.startswith(ERC20_APPROVE_SELECTOR)
    assert len(calldata) == 10 + 64 + 64


# ── Constants Tests ──────────────────────────────────────────

def test_usdc_contracts_exist():
    assert "base-sepolia" in USDC_CONTRACTS
    assert "base-mainnet" in USDC_CONTRACTS
    assert USDC_CONTRACTS["base-sepolia"].startswith("0x")
    assert USDC_CONTRACTS["base-mainnet"].startswith("0x")


# ── Wallet Basic Tests ──────────────────────────────────────

def test_wallet_without_provider():
    wallet = AgentWallet(agent_id="test-bot")
    assert wallet.address == "NOT_CONNECTED"
    assert wallet.balance() == 0.0


def test_pay_without_provider_returns_error():
    wallet = AgentWallet(agent_id="test-bot")
    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=1.0)
    assert isinstance(result, PaymentResult)
    assert result.success is False
    assert "provider" in result.error.lower() or "wallet" in result.error.lower()


def test_pay_calculates_fee_correctly():
    wallet = AgentWallet(agent_id="test-bot")
    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=10.0)
    assert result.fee_usd == 0.1        # 1%
    assert result.service_amount_usd == 9.9


def test_pay_with_custom_fee():
    fee_engine = FeeEngine(FeeConfig(base_fee_percent=0.02))
    wallet = AgentWallet(agent_id="test-bot", fee_engine=fee_engine)
    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=100.0)
    assert result.fee_usd == 2.0
    assert result.service_amount_usd == 98.0


def test_pay_default_currency_is_usdc():
    wallet = AgentWallet(agent_id="test-bot")
    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=1.0)
    assert result.currency == "USDC"


# ── Input Validation Tests ──────────────────────────────────

def test_pay_invalid_address():
    wallet = AgentWallet(agent_id="test-bot")
    result = wallet.pay("invalid_address", amount=1.0)
    assert result.success is False
    assert "invalid" in result.error.lower() or "address" in result.error.lower()


def test_pay_short_address():
    wallet = AgentWallet(agent_id="test-bot")
    result = wallet.pay("0x1234", amount=1.0)
    assert result.success is False


def test_pay_zero_amount():
    wallet = AgentWallet(agent_id="test-bot")
    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=0)
    assert result.success is False
    assert "greater than 0" in result.error.lower() or "amount" in result.error.lower()


def test_pay_negative_amount():
    wallet = AgentWallet(agent_id="test-bot")
    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=-5.0)
    assert result.success is False


# ── Explorer URL Tests ───────────────────────────────────────

def test_explorer_url_sepolia():
    wallet = AgentWallet(agent_id="test", network="base-sepolia")
    url = wallet._get_explorer_url("0xABC123")
    assert "sepolia.basescan.org" in url
    assert "0xABC123" in url


def test_explorer_url_mainnet():
    wallet = AgentWallet(agent_id="test", network="base-mainnet")
    url = wallet._get_explorer_url("0xABC123")
    assert "basescan.org" in url
    assert "sepolia" not in url


# ── PaymentResult Tests ─────────────────────────────────────

def test_payment_result_fields():
    result = PaymentResult(
        success=True,
        transaction_id="0x123",
        amount_usd=1.0,
        fee_usd=0.01,
        service_amount_usd=0.99,
        recipient="0xAAA",
        explorer_url="https://basescan.org/tx/0x123",
        currency="USDC",
    )
    assert result.success is True
    assert result.error == ""
    assert result.currency == "USDC"


# ── Mock Provider Tests ─────────────────────────────────────

class MockProvider:
    """Simulates a wallet provider for testing."""
    def __init__(self, address="0x1234567890abcdef1234567890abcdef12345678", balance=100.0):
        self._address = address
        self._balance = balance
        self.transactions = []

    def get_address(self):
        return self._address

    def get_balance(self, currency="USDC"):
        return self._balance

    def send_transaction(self, to=None, amount=None, token="USDC", **kwargs):
        self.transactions.append({"to": to, "amount": amount, "token": token, **kwargs})
        return "0xMOCK_TX_HASH"


def test_wallet_with_mock_provider():
    provider = MockProvider()
    wallet = AgentWallet(agent_id="mock-bot", wallet_provider=provider)
    assert wallet.address == provider._address
    assert wallet.balance() == 100.0


def test_pay_with_mock_provider_usdc():
    provider = MockProvider()
    fee_engine = FeeEngine(FeeConfig(admin_wallet="0xADMIN567890abcdef1234567890abcdef12345678"))
    wallet = AgentWallet(agent_id="mock-bot", wallet_provider=provider, fee_engine=fee_engine)

    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=10.0, currency="USDC")

    assert result.success is True
    assert result.transaction_id == "0xMOCK_TX_HASH"
    assert result.currency == "USDC"
    assert len(provider.transactions) == 2  # service + fee


def test_pay_without_admin_wallet():
    provider = MockProvider()
    fee_engine = FeeEngine(FeeConfig(admin_wallet=""))
    wallet = AgentWallet(agent_id="mock-bot", wallet_provider=provider, fee_engine=fee_engine)

    result = wallet.pay("0x1234567890abcdef1234567890abcdef12345678", amount=5.0)

    assert result.success is True
    assert len(provider.transactions) == 1  # only service, no fee tx


def test_send_contract_transaction_uses_provider_send_transaction():
    provider = MockProvider()
    wallet = AgentWallet(agent_id="mock-bot", wallet_provider=provider)

    tx_hash = wallet.send_transaction(
        to="0x1234567890abcdef1234567890abcdef12345678",
        data="0xdeadbeef",
        value=0,
    )

    assert tx_hash == "0xMOCK_TX_HASH"
    assert provider.transactions[-1]["to"] == {
        "to": "0x1234567890abcdef1234567890abcdef12345678",
        "value": 0,
        "data": "0xdeadbeef",
    }


def test_approve_erc20_builds_contract_call():
    provider = MockProvider()
    wallet = AgentWallet(agent_id="mock-bot", wallet_provider=provider)

    wallet.approve_erc20(
        token_address="0x3333333333333333333333333333333333333333",
        spender="0x1234567890abcdef1234567890abcdef12345678",
        amount_atomic=123456,
    )

    tx_payload = provider.transactions[-1]["to"]
    assert tx_payload["to"] == "0x3333333333333333333333333333333333333333"
    assert tx_payload["data"].startswith(ERC20_APPROVE_SELECTOR)
