import math

from ddtrace.encoding import Encoder, JSONEncoder
from ddtrace.payload import Payload, PayloadFull
from ddtrace.span import Span

from tests import TracerTestCase

import pytest


class PayloadTestCase(TracerTestCase):
    def test_init(self):
        """
        When calling `Payload.init`
            With an encoder
                We use that encoder
            With no encoder
                We use the default encoder
        """
        default_encoder_type = type(Encoder())

        payload = Payload()
        self.assertIsInstance(payload.encoder, default_encoder_type)

        json_encoder = JSONEncoder()
        payload = Payload(encoder=json_encoder)
        self.assertEqual(payload.encoder, json_encoder)

    def test_add_trace(self):
        """
        When calling `Payload.add_trace`
            With a falsey value
                Nothing is added to the payload
            With a trace
                We encode and add the trace to the payload
                We increment the payload size by the expected amount
        """
        payload = Payload()

        # Add falsey traces
        for val in (False, None, 0, '', [], dict()):
            payload.add_trace(val)
        self.assertEqual(payload.length, 0)
        self.assertTrue(payload.empty)

        # Add a single trace to the payload
        trace = [Span(self.tracer, name='root.span'), Span(self.tracer, name='child.span')]
        payload.add_trace(trace)

        self.assertEqual(payload.length, 1)
        self.assertFalse(payload.empty)

    def test_get_payload(self):
        """
        When calling `Payload.get_payload`
            With no traces
                We return the appropriate data
            With traces
                We return the appropriate data
        """
        payload = Payload()

        # No traces
        self.assertTrue(payload.empty)
        encoded_data = payload.get_payload()
        decoded_data = payload.encoder._decode(encoded_data)
        self.assertEqual(decoded_data, [])

        # Add traces to the payload
        for _ in range(5):
            trace = [Span(self.tracer, name='root.span'), Span(self.tracer, name='child.span')]
            payload.add_trace(trace)

        self.assertEqual(payload.length, 5)
        self.assertFalse(payload.empty)

        # Assert the payload generated from Payload
        encoded_data = payload.get_payload()
        decoded_data = payload.encoder._decode(encoded_data)
        self.assertEqual(len(decoded_data), 5)
        for trace in decoded_data:
            self.assertEqual(len(trace), 2)
            self.assertEqual(trace[0][b'name'], b'root.span')
            self.assertEqual(trace[1][b'name'], b'child.span')

    def test_full(self):
        payload = Payload()

        # Empty
        self.assertTrue(payload.empty)

        # Trace and it's size in bytes
        trace = [Span(self.tracer, 'root.span'), Span(self.tracer, 'child.span')]
        trace_size = len(payload.encoder.encode_trace(trace))

        # Number of traces before we hit the max size limit and are considered full
        num_traces = int(math.floor(payload.max_payload_size / trace_size))

        # Add the traces
        for _ in range(num_traces):
            payload.add_trace(trace)

        # Just confirm
        self.assertEqual(payload.length, num_traces)

        with pytest.raises(PayloadFull):
            payload.add_trace(trace)

        # Just confirm again
        self.assertEqual(payload.length, num_traces)
