import os
import functools
import inspect
from abc import abstractmethod, ABC
from types import TracebackType
from typing import (
    Callable,
    Any,
    ContextManager,
    TypeVar,
    ParamSpec,
    overload,
    Type,
)
from prometheus_client import CollectorRegistry, Counter, Histogram, Gauge, generate_latest

P = ParamSpec("P")
R = TypeVar("R")


class MetricsExporter(ABC):
    """Интерфейс для экспорта метрик (например, в /metrics endpoint)."""

    @property
    @abstractmethod
    def prefix(self) -> str:
        pass

    @property
    @abstractmethod
    def service_name(self) -> str:
        pass

    @abstractmethod
    def get_registry(self) -> CollectorRegistry:
        pass

    @abstractmethod
    def generate_latest_metrics(self) -> bytes:
        pass


class MetricsCollector(ABC):
    """Интерфейс для сбора метрик в коде приложения."""

    @abstractmethod
    def inc_error(self, error_type: str | BaseException = "generic") -> None:
        """Инкремент счетчика ошибок."""
        pass

    @abstractmethod
    def timer(self, task_type: str, long_task: bool = False) -> ContextManager[Any]:
        """
        Возвращает контекстный менеджер для замера времени.
        :param task_type: Метка типа задачи (label).
        :param long_task: Если True, пишет в гистограмму для долгих задач.
        """
        pass

    @overload
    def measure_task(self, func: Callable[P, R]) -> Callable[P, R]: ...

    @overload
    def measure_task(
        self, name: str | None = None, *, long_task: bool = False
    ) -> Callable[[Callable[P, R]], Callable[P, R]]: ...

    def measure_task(
        self, arg: str | Callable[P, R] | None = None, long_task: bool = False
    ) -> Callable[P, R] | Callable[[Callable[P, R]], Callable[P, R]]:
        """
        Универсальный декоратор для замера времени выполнения функций.
        Поддерживает sync и async функции.
        """
        # Случай 1: @metrics.measure
        if callable(arg):
            func = arg
            return self._create_timer_wrapper(func, func.__name__, long_task=False)

        # Случай 2: @metrics.measure("name") или @metrics.measure(long_task=True)
        task_name = arg

        def decorator(f: Callable[P, R]) -> Callable[P, R]:
            name = task_name or f.__name__
            return self._create_timer_wrapper(f, name, long_task=long_task)

        return decorator

    def _create_timer_wrapper(self, func: Callable[P, R], task_name: str, long_task: bool) -> Callable[P, R]:
        if inspect.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
                with self.timer(task_name, long_task=long_task):
                    return await func(*args, **kwargs)  # type: ignore

            return async_wrapper  # type: ignore
        else:

            @functools.wraps(func)
            def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
                with self.timer(task_name, long_task=long_task):
                    return func(*args, **kwargs)

            return sync_wrapper

    @overload
    def track_exception(self, func: Callable[P, R]) -> Callable[P, R]:
        pass

    @overload
    def track_exception(self, *, exclude: list[Type[BaseException]] | None = None) -> "ExceptionTracker":
        pass

    @abstractmethod
    def track_exception(
        self, arg: Callable[P, R] | None = None, *, exclude: list[Type[BaseException]] | None = None
    ) -> Any:
        pass


class ExceptionTracker:
    """
    Гибридный класс: Context Manager + Decorator.
    Используется внутри track_exception.
    """

    def __init__(
        self,
        metrics: MetricsCollector,
        exclude: list[Type[BaseException]] | None = None,
    ):
        self.metrics = metrics
        self.exclude = tuple(exclude) if exclude else ()

    def _handle_exception(self, exc_val: BaseException) -> None:
        if not isinstance(exc_val, self.exclude):
            self.metrics.inc_error(exc_val)

    def __enter__(self) -> "ExceptionTracker":
        return self

    def __exit__(
        self,
        exc_type: Type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if exc_val:
            self._handle_exception(exc_val)

    async def __aenter__(self) -> "ExceptionTracker":
        return self

    async def __aexit__(
        self,
        exc_type: Type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if exc_val:
            self._handle_exception(exc_val)

    def __call__(self, func: Callable[P, R]) -> Callable[P, R]:
        if inspect.iscoroutinefunction(func):

            @functools.wraps(func)
            async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
                async with self:
                    return await func(*args, **kwargs)

            return async_wrapper  # type: ignore
        else:

            @functools.wraps(func)
            def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
                with self:
                    return func(*args, **kwargs)

            return sync_wrapper


class PrometheusMetrics(MetricsCollector, MetricsExporter):
    """
    Реализация метрик на базе prometheus-client.
    Разделяет задачи на Fast (API, легкая логика) и Long (Background tasks, crons).
    """

    BUCKETS_FAST: tuple[float, ...] = (0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0)
    BUCKETS_LONG: tuple[float, ...] = (1.0, 5.0, 10.0, 30.0, 60.0, 300.0, 600.0, 1800.0, 3600.0, 7200.0, 14400.0)

    def __init__(
        self,
        service_name: str,
        version: str | None = None,
        env: str | None = None,
        prefix: str = "fasttelemetry",
    ) -> None:
        self._service_name = service_name
        self._env = env or os.getenv("APP_ENV", "dev")
        self._version = version or os.getenv("APP_VERSION", "unknown")
        self._prefix = prefix.rstrip("_")

        self._registry = CollectorRegistry(auto_describe=True)

        self._app_info: Gauge = self._create_app_info()
        self._business_errors: Counter = self._create_business_errors()
        self._task_duration_fast: Histogram = self._create_fast_histogram()
        self._task_duration_long: Histogram = self._create_long_histogram()

        self._set_initial_values()

    def _create_app_info(self) -> Gauge:
        return Gauge(
            name=f"{self._prefix}_app_info",
            documentation="Application information",
            labelnames=["service", "env", "version"],
            registry=self._registry,
        )

    def _create_business_errors(self) -> Counter:
        return Counter(
            name=f"{self._prefix}_business_errors_total",
            documentation="Total count of business logic errors",
            labelnames=["error_type"],
            registry=self._registry,
        )

    def _create_fast_histogram(self) -> Histogram:
        return Histogram(
            name=f"{self._prefix}_task_processing_seconds",
            documentation="Time spent processing internal tasks (fast)",
            labelnames=["task_type"],
            registry=self._registry,
            buckets=self.BUCKETS_FAST,
        )

    def _create_long_histogram(self) -> Histogram:
        return Histogram(
            name=f"{self._prefix}_long_task_processing_seconds",
            documentation="Time spent processing long-running tasks",
            labelnames=["task_type"],
            registry=self._registry,
            buckets=self.BUCKETS_LONG,
        )

    def _set_initial_values(self) -> None:
        """Устанавливает статические значения при старте."""
        self._app_info.labels(service=self._service_name, env=self._env, version=self._version).set(1)

    @property
    def service_name(self) -> str:
        return self._service_name

    @property
    def prefix(self) -> str:
        return self._prefix

    def inc_error(self, error_type: str | BaseException = "generic") -> None:
        if isinstance(error_type, BaseException):
            error_type = type(error_type).__name__
        self._business_errors.labels(error_type=error_type).inc()

    def track_exception(
        self, arg: Callable[P, R] | None = None, *, exclude: list[Type[BaseException]] | None = None
    ) -> Any:
        """
        Декоратор и контекстный менеджер для подсчета исключений.
        """
        tracker = ExceptionTracker(self, exclude=exclude)

        if callable(arg):
            return tracker(arg)

        return tracker

    def timer(self, task_type: str, long_task: bool = False) -> ContextManager[Any]:
        if long_task:
            return self._task_duration_long.labels(task_type=task_type).time()
        return self._task_duration_fast.labels(task_type=task_type).time()

    def get_registry(self) -> CollectorRegistry:
        return self._registry

    def generate_latest_metrics(self) -> bytes:
        return generate_latest(self._registry)
