"""Teaching example: adapt a response without hiding business rejections.

Run: python sdk_contract.py
Python 3.10+, standard library only. No real payments or network requests.
"""
from dataclasses import dataclass
import unittest


# BEGIN adapter
@dataclass(frozen=True)
class Result:
    code: str
    data: dict


class FundsAPI:
    def __init__(self, transport):
        self.transport = transport

    def route(self):
        return "/v2/funds/query"

    def query(self, record_id, *, version="v2"):
        reply = self.transport.request(
            self.route(), {"record_id": record_id}, version=version
        )
        # Transport exceptions propagate; a business rejection remains data.
        body = reply["body"]
        return Result(code=body["code"], data=body["data"])


def poll_query(query, *, attempts, sleep, interval=0.1):
    if type(attempts) is not int or attempts < 1:
        raise ValueError("attempts must be a positive integer")
    for index in range(attempts):
        result = query()
        if result.code != "PENDING":
            return result
        if index + 1 < attempts:
            sleep(interval)
    raise TimeoutError("query remained PENDING")
# END adapter


class FakeTransport:
    def __init__(self, code="OK", error=None):
        self.code, self.error, self.calls = code, error, []

    def request(self, path, payload, *, version):
        self.calls.append((path, payload, version))
        if self.error:
            raise self.error
        return {"body": {"code": self.code, "data": {"id": payload["record_id"]}}}


class CompatibilityTests(unittest.TestCase):
    def test_old_caller_reads_code_and_default_version(self):
        transport = FakeTransport()
        self.assertEqual(FundsAPI(transport).query("r1").code, "OK")
        self.assertEqual(transport.calls, [("/v2/funds/query", {"record_id": "r1"}, "v2")])

    def test_subclass_route_and_explicit_version_survive(self):
        class LegacyAPI(FundsAPI):
            def route(self):
                return "/legacy/query"
        transport = FakeTransport()
        LegacyAPI(transport).query("r1", version="v1")
        self.assertEqual(transport.calls[0], ("/legacy/query", {"record_id": "r1"}, "v1"))

    def test_business_rejection_is_observable(self):
        self.assertEqual(FundsAPI(FakeTransport("DENIED")).query("r1").code, "DENIED")

    def test_transport_timeout_is_not_business_rejection(self):
        with self.assertRaises(TimeoutError):
            FundsAPI(FakeTransport(error=TimeoutError())).query("r1")

    def test_poll_pending_then_success(self):
        responses = iter([Result("PENDING", {}), Result("OK", {})])
        sleeps = []
        self.assertEqual(poll_query(lambda: next(responses), attempts=3, sleep=sleeps.append).code, "OK")
        self.assertEqual(sleeps, [0.1])

    def test_poll_business_rejection_is_terminal(self):
        sleeps = []
        self.assertEqual(poll_query(lambda: Result("DENIED", {}), attempts=3, sleep=sleeps.append).code, "DENIED")
        self.assertEqual(sleeps, [])

    def test_poll_exhaustion_has_no_final_sleep(self):
        calls, sleeps = [], []
        def pending():
            calls.append(1)
            return Result("PENDING", {})
        with self.assertRaises(TimeoutError):
            poll_query(pending, attempts=3, sleep=sleeps.append)
        self.assertEqual(len(calls), 3)
        self.assertEqual(sleeps, [0.1, 0.1])


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