import asyncio
import functools
import inspect
import logging
import os
import socket
from abc import abstractmethod
from types import TracebackType
from typing import Type, Callable

from prometheus_client import start_http_server, push_to_gateway

from ..core import PrometheusMetrics, MetricsCollector, P, R

logger = logging.getLogger("fast-telemetry")


class IJobMetricsCollector(MetricsCollector):
    @abstractmethod
    def track_job(
        self, gateway_url: str, timeout: float = 5.0, grouping_key: dict[str, str] | None = None
    ) -> "UnifiedJobPusher":
        """
        Создает трекер для job-а.
        Может использоваться как контекстный менеджер или декоратор.
        """
        pass

    @abstractmethod
    def start_server(self, port: int = 8000, addr: str = "0.0.0.0") -> None:
        pass


class UnifiedJobPusher:
    """
    Гибридный объект: Context Manager + Decorator.
    Отправляет метрики в Pushgateway при завершении работы блока или функции.
    """

    def __init__(self, metrics: "WorkerMetrics", gateway_url: str, timeout: float, grouping_key: dict[str, str] | None):
        self.metrics = metrics
        self.gateway_url = gateway_url
        self.timeout = timeout
        # Группируем по инстансу, чтобы воркеры не перезатирали метрики друг друга
        self.grouping_key = grouping_key or {"instance": self.metrics.instance_id}

    def _push(self) -> None:
        """Безопасная отправка метрик (не должна ронять скрипт)."""
        try:
            push_to_gateway(
                self.gateway_url,
                job=self.metrics.service_name,
                registry=self.metrics.get_registry(),
                grouping_key=self.grouping_key,
                timeout=self.timeout,
            )
        except Exception:
            logger.exception("Failed to push metrics to {}", self.gateway_url)
            logger.warning("Render metrics:\n{}", self.metrics.generate_latest_metrics())

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

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

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

    async def __aexit__(
        self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
    ) -> None:
        await asyncio.to_thread(self._push)

    def __call__(self, func: Callable[[P], R]) -> Callable[[P], R]:
        """Позволяет использовать объект как декоратор @metrics.track_job(...)"""
        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)  # type: ignore

            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 WorkerMetrics(PrometheusMetrics, IJobMetricsCollector):
    def __init__(self, service_name: str, version: str | None = None, env: str | None = None) -> None:
        super().__init__(service_name=service_name, version=version, env=env)
        self._is_server_started = False
        # Уникальный ID для каждого запуска скрипта/пода
        self._instance_id = f"{socket.gethostname()}-{os.getpid()}"

    @property
    def instance_id(self) -> str:
        return self._instance_id

    def start_server(self, port: int = 8000, addr: str = "0.0.0.0") -> None:
        """
        Запускает HTTP сервер (Daemon thread) для pull-модели (если нужно).
        """
        if self._is_server_started:
            logger.warning("Metrics server is already running")
            return

        start_http_server(port, addr=addr, registry=self._registry)
        self._is_server_started = True

    def track_job(
        self, gateway_url: str, timeout: float = 5.0, grouping_key: dict[str, str] | None = None
    ) -> UnifiedJobPusher:
        """
        Возвращает объект, который работает и как ContextManager, и как Decorator.
        """
        return UnifiedJobPusher(metrics=self, gateway_url=gateway_url, timeout=timeout, grouping_key=grouping_key)
