import os
import random

import pytest

from clarabot_nano.common.protocol.request import NanoRequestContext
from clarabot_nano.server.base.handler.base import AbstractNanoServerHandler
from clarabot_nano.utility.serialize import msgpack_dumps


def test_server_handler_base_get_s_hmac_part_beginning():
    h = AbstractNanoServerHandler(None, None, {NanoRequestContext.S_HMAC_PARTS: [24, 48]}, None)
    cs = [2]
    for _ in range(24):
        cs.append(cs[-1] + random.randint(33, 127))
    for count in cs:
        h.count = count
        h.cacheable = random.random() > 0.5
        if h.cacheable:
            h.cache_key = os.urandom(17)
        else:
            h.transfer_key = os.urandom(17)
        covered = []
        for part in range(1, count):
            start = h._get_s_hmac_part_beginning(part)
            if start is not None:
                covered.extend(range(start, part))
        assert covered == list(range(count - 1))


def test_server_handler_base_approximate_part_slicing():
    h = AbstractNanoServerHandler(
        None,
        None,
        {
            NanoRequestContext.PART_SIZE: 1_000_000,
            NanoRequestContext.PART_APPROX: [750_000, 250_000],
        },
        None,
    )
    tmp100 = "asdfqwerxy" * 10
    tmp200 = tmp100 * 2
    tmp1000 = tmp200 * 5
    tmp5000 = tmp1000 * 5
    tmp25000 = tmp5000 * 5
    tmp300000 = tmp25000 * 12

    def _calc(x):
        return len(msgpack_dumps(x))

    def _check(src, ol):
        slices = h._approximate_part_slicing(src, _calc, ol)
        slice_start = 0
        for s in slices:
            t = max(0, slice_start - ol)
            assert src[t : t + len(s)] == s
            slice_start = t + len(s)
        for s in slices:
            assert (
                len(msgpack_dumps(s))
                <= h.context[NanoRequestContext.PART_SIZE] + h.context[NanoRequestContext.PART_APPROX][1]
            )
        assert slices == h._approximate_part_slicing(src, _calc, ol)

    # uniform item size
    for count in [100, 2000, 5000, 15000]:
        for overlap in [0, 4, 7, 16, 42]:
            for data in [tmp100, tmp200, tmp1000, tmp5000]:
                _check([{"id": i, "data": data} for i in range(count)], overlap)

    # varying item size
    for count in [100, 2000, 5000, 15000]:
        for overlap in [0, 4, 7, 16]:
            for d0, d1 in [(tmp200, tmp25000), (tmp25000, tmp200)]:
                _check([{"id": i, "data": (d0 if i < count // 2 else d1)} for i in range(count)], overlap)

    # huge items
    for count in [2, 10]:
        for overlap in [0, 1]:
            _check([{"id": i, "data": tmp300000} for i in range(count)], overlap)

    with pytest.raises(OverflowError, match="Overlap ate all the space"):
        _check([{"id": i, "data": tmp25000} for i in range(250)], 100)
