from collections import deque
import uuid
import tracing.config as config
import requests
import json
import random


class LocalContext(object):
    """
    Class that stores context about the current nested set of traces
    It keeps track of parent-child relationships

    If a span exists, it has a corresponding empty child map
    """

    def __init__(self, trace_id=None, span_id=None, service="Default"):
        if span_id is None:
            span_id = random.getrandbits(32)

        if trace_id is None:
            trace_id = random.getrandbits(32)

        self.trace_id = trace_id
        self.child_map = {}
        self.spans_by_id = {}
        self.ordered_trace_deque = deque([span_id])
        self.child_map[span_id] = []
        self.service = service
        self.trace_buffer = {self.trace_id: []}

    def get_current_span_from_parent_tree(self):
        """
        Returns the trace that is currently at the lowest level
        in the dependency tree
        :return:
        """
        return self.ordered_trace_deque[-1]

    def remove_current_span_from_parent_tree(self):
        """
        Removes a trace from the lowest level of the dependency tree.
        :return:
        """
        self.ordered_trace_deque.pop()

    def add_new_span_to_parent_tree(self, new_trace=None):
        if new_trace is None:
            new_trace = random.getrandbits(32)
        self.child_map[new_trace] = []
        self.child_map[self.get_current_span_from_parent_tree()].append(new_trace)
        self.ordered_trace_deque.append(new_trace)

    def add_span_to_trace_buffer(self, span_record: config.SPAN_RECORD):
        span_as_map = config.convert_span_record_to_key_value(span_record)
        self.trace_buffer[self.trace_id].append(span_as_map)
        self.spans_by_id[span_as_map["span_id"]] = span_as_map

        if len(self.trace_buffer[self.trace_id]) >= config.DEFAULT_REQUEST_BUFFER_SIZE:
            self.flush_buffer_to_agent()

    def _get_fully_described_child(self, child):
        span_as_map = self.spans_by_id[child]
        span_as_map["id"] = span_as_map["span_id"]
        span_as_map["children"] = [self._get_fully_described_child(child) for child in span_as_map["children"]]

        return span_as_map

    def _get_fully_described_trace(self, trace):
        # find the root span
        root = None

        for span in trace:
            if span["parent_id"] == 0:
                root = span
                break
        
        if not root:
            raise RuntimeException("no root for trace_id %s" % trace['trace_id'])

        root["children"] = [self._get_fully_described_child(child) for child in root["children"]]
        root["meta"] = {"layer": "sigmoid", "neurons": 4, "next_layer": "curvilinear"}
        root["id"] = root["span_id"]

        return root


    def flush_buffer_to_agent(self):
        # Trace payload should look like
        # { "traces": [ { "id': "xyz", "trace": {} }, ...  ] }


        trace_list = {"traces": []}

        for trace_id, trace in self.trace_buffer.items():
            trace_list["traces"].append(
                {"id": trace_id, "trace": self._get_fully_described_trace(trace)}
            )
        
        print(json.dumps(trace_list))
        requests.post(url=config.DEFAULT_AGENT_URL, json=trace_list)
        self.trace_buffer.clear()
        self.trace_buffer[self.trace_id] = []
