# Python 3, standard library only. Set ARBISCAN_API_KEY on your server.
import json
import os
import random
import time
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen


def read_odds():
    key = os.environ['ARBISCAN_API_KEY']
    first = dict(sport='Football', bookmakers='Betclic', markets='h2h',
                 max_age_seconds=900, limit=500)
    for restart in range(3):
        events, seen = [], set()
        cursor, snapshot, retries = None, None, 0
        started = time.monotonic()
        while True:
            query = dict(cursor=cursor, limit=500) if cursor else first
            request = Request('https://api.arbiscan.pl/v1/odds?' + urlencode(query),
                              headers={'X-API-Key': key})
            try:
                response = urlopen(request, timeout=30)
            except HTTPError as error:
                response = error
            with response:
                status, headers = response.status, response.headers
                try:
                    body = json.load(response)
                except (ValueError, UnicodeError):
                    body = {}  # A proxy may return HTML.
            code = body.get('code')
            if status == 409 and code in ('snapshot_changed', 'cursor_version_expired'):
                break  # Discard this buffer and restart from page one.
            if status in (429, 503) and code != 'total_quota_exceeded' and retries < 3:
                retry_after = headers.get('Retry-After')
                try:
                    delay = float(retry_after)
                except (TypeError, ValueError):
                    try:
                        delay = parsedate_to_datetime(retry_after).timestamp() - time.time()
                    except (TypeError, ValueError, AttributeError):
                        delay = 2 ** retries
                delay = max(0, delay)
                if time.monotonic() - started + delay >= 230:
                    raise RuntimeError('Retry later; discard partial traversal')
                retries += 1
                time.sleep(delay + random.random() * 0.25)
                continue
            if not 200 <= status < 300:
                raise RuntimeError(f'{status} {code or "non_json_error"}')
            retries = 0
            if snapshot is not None and snapshot != body['snapshot']:
                raise RuntimeError('Snapshot mismatch')
            snapshot = body['snapshot']
            events.extend(body['events'])
            if not body['has_more']:
                return events
            cursor = body['next_cursor']
            if not cursor or cursor in seen:
                raise RuntimeError('Invalid continuation')
            seen.add(cursor)
            if time.monotonic() - started >= 230:
                raise RuntimeError('Traversal too slow; discard partial results')
    raise RuntimeError('Restart budget exhausted')


if __name__ == '__main__':
    print(json.dumps(read_odds()))
