import time
from typing import Optional

from opentelemetry.metrics import (
    Meter,
    Counter,
    Histogram,
    UpDownCounter,
    set_meter_provider,
)
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.view import View, ExplicitBucketHistogramAggregation
from opentelemetry.exporter.prometheus import PrometheusMetricReader

from .exemplar import get_exemplar
from .tracker import Result
from ..objectives import Objective, ObjectiveLatency
from ..constants import (
    CONCURRENCY_NAME,
    CONCURRENCY_DESCRIPTION,
    COUNTER_DESCRIPTION,
    COUNTER_NAME,
    HISTOGRAM_DESCRIPTION,
    HISTOGRAM_NAME,
    BUILD_INFO_NAME,
    BUILD_INFO_DESCRIPTION,
    SERVICE_NAME,
    OBJECTIVE_NAME,
    OBJECTIVE_PERCENTILE,
    OBJECTIVE_LATENCY_THRESHOLD,
)
from ..settings import get_settings


class OpenTelemetryTracker:
    """Tracker for OpenTelemetry."""

    __counter_instance: Counter
    __histogram_instance: Histogram
    __up_down_counter_build_info_instance: UpDownCounter
    __up_down_counter_concurrency_instance: UpDownCounter

    def __init__(self):
        exporter = PrometheusMetricReader("")
        view = View(
            name=HISTOGRAM_NAME,
            description=HISTOGRAM_DESCRIPTION,
            instrument_name=HISTOGRAM_NAME,
            aggregation=ExplicitBucketHistogramAggregation(
                boundaries=get_settings()["histogram_buckets"]
            ),
        )
        meter_provider = MeterProvider(metric_readers=[exporter], views=[view])
        set_meter_provider(meter_provider)
        meter = meter_provider.get_meter(name="autometrics")
        self.__counter_instance = meter.create_counter(
            name=COUNTER_NAME, description=COUNTER_DESCRIPTION
        )
        self.__histogram_instance = meter.create_histogram(
            name=HISTOGRAM_NAME,
            description=HISTOGRAM_DESCRIPTION,
            unit="seconds",
        )
        self.__up_down_counter_build_info_instance = meter.create_up_down_counter(
            name=BUILD_INFO_NAME,
            description=BUILD_INFO_DESCRIPTION,
        )
        self.__up_down_counter_concurrency_instance = meter.create_up_down_counter(
            name=CONCURRENCY_NAME,
            description=CONCURRENCY_DESCRIPTION,
        )
        self._has_set_build_info = False

    def __count(
        self,
        function: str,
        module: str,
        caller_module: str,
        caller_function: str,
        objective: Optional[Objective],
        exemplar: Optional[dict],
        result: Result,
        inc_by: int = 1,
    ):
        objective_name = "" if objective is None else objective.name
        percentile = (
            ""
            if objective is None or objective.success_rate is None
            else objective.success_rate.value
        )
        self.__counter_instance.add(
            inc_by,
            attributes={
                "function": function,
                "module": module,
                "result": result.value,
                "caller.module": caller_module,
                "caller.function": caller_function,
                OBJECTIVE_NAME: objective_name,
                OBJECTIVE_PERCENTILE: percentile,
                SERVICE_NAME: get_settings()["service_name"],
            },
        )

    def __histogram(
        self,
        function: str,
        module: str,
        start_time: float,
        objective: Optional[Objective],
        exemplar: Optional[dict],
    ):
        duration = time.time() - start_time

        objective_name = "" if objective is None else objective.name
        latency = None if objective is None else objective.latency
        percentile = ""
        threshold = ""

        if latency is not None:
            threshold = latency[0].value
            percentile = latency[1].value

        self.__histogram_instance.record(
            duration,
            attributes={
                "function": function,
                "module": module,
                SERVICE_NAME: get_settings()["service_name"],
                OBJECTIVE_NAME: objective_name,
                OBJECTIVE_PERCENTILE: percentile,
                OBJECTIVE_LATENCY_THRESHOLD: threshold,
            },
        )

    def set_build_info(self, commit: str, version: str, branch: str):
        if not self._has_set_build_info:
            self._has_set_build_info = True
            self.__up_down_counter_build_info_instance.add(
                1.0,
                attributes={
                    "commit": commit,
                    "version": version,
                    "branch": branch,
                    SERVICE_NAME: get_settings()["service_name"],
                },
            )

    def start(
        self,
        function: str,
        module: str,
        track_concurrency: Optional[bool] = False,
    ):
        """Start tracking metrics for a function call."""
        if track_concurrency:
            self.__up_down_counter_concurrency_instance.add(
                1.0,
                attributes={
                    "function": function,
                    "module": module,
                    SERVICE_NAME: get_settings()["service_name"],
                },
            )

    def finish(
        self,
        start_time: float,
        function: str,
        module: str,
        caller_module: str,
        caller_function: str,
        result: Result = Result.OK,
        objective: Optional[Objective] = None,
        track_concurrency: Optional[bool] = False,
    ):
        """Finish tracking metrics for a function call."""

        exemplar = None
        # Currently, exemplars are only supported by prometheus-client
        # https://github.com/autometrics-dev/autometrics-py/issues/41
        # if get_settings()["exemplars"]:
        #     exemplar = get_exemplar()
        self.__count(
            function,
            module,
            caller_module,
            caller_function,
            objective,
            exemplar,
            result,
        )
        self.__histogram(function, module, start_time, objective, exemplar)
        if track_concurrency:
            self.__up_down_counter_concurrency_instance.add(
                -1.0,
                attributes={
                    "function": function,
                    "module": module,
                    SERVICE_NAME: get_settings()["service_name"],
                },
            )

    def initialize_counters(
        self,
        function: str,
        module: str,
        objective: Optional[Objective] = None,
    ):
        """Initialize tracking metrics for a function call at zero."""
        caller_module = ""
        caller_function = ""
        self.__count(
            function,
            module,
            caller_module,
            caller_function,
            objective,
            None,
            Result.OK,
            0,
        )
        self.__count(
            function,
            module,
            caller_module,
            caller_function,
            objective,
            None,
            Result.ERROR,
            0,
        )
