import unittest

from solana_usdc_verify import VerificationResult, amount_to_units, verify_payment


MINT = "Mint111111111111111111111111111111111111111"
RECIPIENT = "Recipient1111111111111111111111111111111111"
SIGNATURE = "sig-123"


def transaction(amount: int = 1_250_000, *, error=None):
    return {
        "slot": 100,
        "meta": {
            "err": error,
            "preTokenBalances": [{"accountIndex": 4, "mint": MINT, "owner": RECIPIENT, "uiTokenAmount": {"amount": "0"}}],
            "postTokenBalances": [{"accountIndex": 4, "mint": MINT, "owner": RECIPIENT, "uiTokenAmount": {"amount": str(amount)}}],
        },
    }


class FakeClient:
    def __init__(self, statuses, tx= None):
        self.statuses = list(statuses)
        self.tx = tx
        self.calls = 0

    def get_signature_status(self, signature):
        index = min(self.calls, len(self.statuses) - 1)
        return self.statuses[index]

    def get_transaction(self, signature):
        self.calls += 1
        return self.tx


class SolanaUsdcVerifierTests(unittest.TestCase):
    def test_amount_conversion_is_exact(self):
        self.assertEqual(amount_to_units("1.25"), 1_250_000)
        self.assertEqual(amount_to_units("0"), 0)
        with self.assertRaises(ValueError):
            amount_to_units("0.0000001")

    def test_verified_confirmed_payment(self):
        client = FakeClient([{"confirmationStatus": "confirmed", "confirmations": 2}], transaction())
        result = verify_payment(client, SIGNATURE, RECIPIENT, 1_250_000, mint=MINT, timeout_seconds=0, sleep=lambda _: None)
        self.assertIsInstance(result, VerificationResult)
        self.assertTrue(result.verified)
        self.assertEqual(result.reason, "verified")

    def test_wrong_amount_is_rejected(self):
        client = FakeClient([{"confirmationStatus": "confirmed", "confirmations": 2}], transaction(1_000_000))
        result = verify_payment(client, SIGNATURE, RECIPIENT, 1_250_000, mint=MINT, timeout_seconds=0, sleep=lambda _: None)
        self.assertFalse(result.verified)
        self.assertEqual(result.reason, "amount_or_recipient_mismatch")

    def test_failed_transaction_is_rejected(self):
        client = FakeClient([{"confirmationStatus": "confirmed", "confirmations": 2}], transaction(error={"InstructionError": [0, "Custom"]}))
        result = verify_payment(client, SIGNATURE, RECIPIENT, 1_250_000, mint=MINT, timeout_seconds=0, sleep=lambda _: None)
        self.assertFalse(result.verified)
        self.assertEqual(result.reason, "transaction_failed")

    def test_waits_for_required_confirmations(self):
        client = FakeClient([
            {"confirmationStatus": "confirmed", "confirmations": 1},
            {"confirmationStatus": "confirmed", "confirmations": 3},
        ], transaction())
        result = verify_payment(client, SIGNATURE, RECIPIENT, 1_250_000, mint=MINT, min_confirmations=3, timeout_seconds=10, poll_interval_seconds=0, sleep=lambda _: None)
        self.assertTrue(result.verified)
        self.assertEqual(result.attempts, 2)

    def test_pending_signature_times_out(self):
        clock_values = iter([0.0, 0.0, 0.0, 2.0])
        client = FakeClient([None], None)
        result = verify_payment(client, SIGNATURE, RECIPIENT, 1_250_000, mint=MINT, timeout_seconds=1, poll_interval_seconds=0, clock=lambda: next(clock_values), sleep=lambda _: None)
        self.assertFalse(result.verified)
        self.assertEqual(result.reason, "timeout")


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