"""Bounded offline outbox and deterministic retry-state model."""
from collections import deque
from random import Random


class Link:
    def __init__(self, capacity=4):
        if capacity <= 0:
            raise ValueError("positive capacity required")
        self.capacity = capacity
        self.outbox = deque()
        self.dropped = 0
        self.state = "offline"
        self.attempt = 0
        self.retry_at = 0.0
        self.random = Random(7)

    def sample(self, sequence):
        if len(self.outbox) == self.capacity:
            self.outbox.popleft()
            self.dropped += 1
        self.outbox.append(sequence)

    def failed(self, now):
        nominal = min(16.0, 2.0 ** min(self.attempt, 4))
        delay = self.random.uniform(nominal / 2, nominal)
        self.attempt += 1
        self.retry_at = now + delay
        self.state = "backoff"
        return delay

    def may_connect(self, now):
        return self.state in ("offline", "backoff") and now >= self.retry_at

    def connected(self):
        self.state = "online"
        self.attempt = 0

    def flush(self):
        if self.state != "online":
            return []
        result = list(self.outbox)
        self.outbox.clear()
        return result


def main():
    link = Link()
    now = 0.0
    delays = []
    for _ in range(12):
        assert link.may_connect(now)
        delay = link.failed(now)
        delays.append(delay)
        assert not link.may_connect(now)
        now = link.retry_at
    assert all(0 < delay <= 16 for delay in delays)
    for seq in range(10):
        link.sample(seq)
    assert len(link.outbox) == 4 and link.dropped == 6
    assert link.flush() == []
    link.connected()
    assert link.flush() == [6, 7, 8, 9]
    assert link.flush() == [] and link.attempt == 0
    print("offline=10 records, capacity=4, dropped=6, recovered=[6,7,8,9]")
    print("PASS")


if __name__ == "__main__":
    main()
