# -*- coding: utf-8 -*-
"""Location: ./tests/unit/mcpgateway/services/test_metrics.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

Unit tests for Metrics service.
"""

# Standard
from unittest.mock import MagicMock, patch

# Third-Party
import pytest
from prometheus_client import REGISTRY

# First-Party
from mcpgateway.services.metrics import (
    circuit_breaker_open_counter,
    setup_metrics,
    tool_timeout_counter,
)

_METRIC_GAUGE_NAMES = ("app_info", "database_info", "http_pool_max_connections", "http_pool_max_keepalive_connections")


def _unregister_metric_gauges():
    """Remove known metric gauges from the global registry."""
    for name in _METRIC_GAUGE_NAMES:
        collector = REGISTRY._names_to_collectors.get(name)
        if collector is not None:
            try:
                REGISTRY.unregister(collector)
            except Exception:
                pass


@pytest.fixture(autouse=True)
def _clean_prometheus_registry():
    """Clean up metric gauges before and after each test to avoid duplicates."""
    _unregister_metric_gauges()
    yield
    _unregister_metric_gauges()


# ---------- Global counters ----------


def test_tool_timeout_counter_exists():
    assert tool_timeout_counter is not None
    assert tool_timeout_counter._name == "tool_timeout"


def test_circuit_breaker_open_counter_exists():
    assert circuit_breaker_open_counter is not None
    assert circuit_breaker_open_counter._name == "circuit_breaker_open"


# ---------- setup_metrics enabled ----------


def test_setup_metrics_enabled_sqlite():
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "sqlite:///./test.db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = ""
        inst = MagicMock()
        mock_inst_cls.return_value = inst
        setup_metrics(app)
    inst.instrument.assert_called_once_with(app)
    # Auth-gated endpoint is registered directly via app.get (not instrumentator.expose)
    app.get.assert_called()


def test_setup_metrics_enabled_postgresql():
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "postgresql://user:pass@localhost/db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = ""
        inst = MagicMock()
        mock_inst_cls.return_value = inst
        setup_metrics(app)
    inst.instrument.assert_called_once()


def test_setup_metrics_enabled_postgresql_psycopg():
    """Driver-qualified postgresql+psycopg:// URLs must be detected as postgresql."""
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "postgresql+psycopg://user:pass@localhost/db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = ""
        inst = MagicMock()
        mock_inst_cls.return_value = inst
        setup_metrics(app)
    inst.instrument.assert_called_once()


def test_setup_metrics_enabled_unknown_db():
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "cockroachdb://localhost/db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = ""
        inst = MagicMock()
        mock_inst_cls.return_value = inst
        setup_metrics(app)
    inst.instrument.assert_called_once()


def test_setup_metrics_with_custom_labels():
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "env=prod,region=us-east", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "sqlite:///./test.db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = ""
        inst = MagicMock()
        mock_inst_cls.return_value = inst
        setup_metrics(app)
    inst.instrument.assert_called_once()


def test_setup_metrics_with_excluded_handlers():
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "sqlite:///./test.db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = "/health,/ready"
        inst = MagicMock()
        mock_inst_cls.return_value = inst
        setup_metrics(app)
    inst.instrument.assert_called_once()


def test_setup_metrics_postgres_prefix():
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "postgres://user:pass@localhost/db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = ""
        inst = MagicMock()
        mock_inst_cls.return_value = inst
        setup_metrics(app)
    inst.instrument.assert_called_once()


# ---------- setup_metrics disabled ----------


def test_setup_metrics_disabled():
    app = MagicMock()
    with patch("mcpgateway.services.metrics.settings") as mock_settings:
        mock_settings.ENABLE_METRICS = False
        setup_metrics(app)
    # Should register a disabled endpoint
    app.get.assert_called_once()


# ---------- update_http_pool_metrics ----------


def test_update_http_pool_metrics_function_stored():
    app = MagicMock()
    app.state = MagicMock()
    with (
        patch.dict("os.environ", {"ENABLE_METRICS": "true", "METRICS_CUSTOM_LABELS": "", "METRICS_EXCLUDED_HANDLERS": ""}),
        patch("mcpgateway.services.metrics.settings") as mock_settings,
        patch("mcpgateway.services.metrics.Instrumentator") as mock_inst_cls,
    ):
        mock_settings.database_url = "sqlite:///./test.db"
        mock_settings.METRICS_EXCLUDED_HANDLERS = ""
        mock_inst_cls.return_value = MagicMock()
        setup_metrics(app)

    # The update function should have been stored on app.state
    assert hasattr(app.state, "update_http_pool_metrics")
