from __future__ import annotations

import os
from unittest import TestCase
from unittest.mock import patch

from fastapi import APIRouter
from fastapi.testclient import TestClient
from sqlalchemy import text

from govoplan_core.core.registry import PlatformRegistry
from govoplan_core.db.query_metrics import collect_query_metrics
from govoplan_core.db.session import create_database_engine
from govoplan_core.server.fastapi import create_govoplan_app


class QueryMetricsTests(TestCase):
    def test_non_sqlite_engine_uses_bounded_pool_configuration(self) -> None:
        configured = {
            "GOVOPLAN_DB_POOL_SIZE": "8",
            "GOVOPLAN_DB_MAX_OVERFLOW": "16",
            "GOVOPLAN_DB_POOL_TIMEOUT_SECONDS": "45",
            "GOVOPLAN_DB_POOL_RECYCLE_SECONDS": "900",
        }
        engine = object()
        with (
            patch.dict(os.environ, configured, clear=False),
            patch(
                "govoplan_core.db.session.create_engine",
                return_value=engine,
            ) as create_engine,
            patch(
                "govoplan_core.db.session.instrument_engine",
                side_effect=lambda value: value,
            ),
        ):
            result = create_database_engine(
                "postgresql+psycopg://govoplan@example/govoplan",
            )

        self.assertIs(engine, result)
        create_engine.assert_called_once_with(
            "postgresql+psycopg://govoplan@example/govoplan",
            pool_pre_ping=True,
            connect_args={},
            pool_size=8,
            max_overflow=16,
            pool_timeout=45,
            pool_recycle=900,
        )

    def test_non_sqlite_engine_rejects_pool_values_outside_safe_bounds(self) -> None:
        invalid_values = (
            ("GOVOPLAN_DB_POOL_SIZE", "0"),
            ("GOVOPLAN_DB_MAX_OVERFLOW", "201"),
            ("GOVOPLAN_DB_POOL_TIMEOUT_SECONDS", "not-a-number"),
            ("GOVOPLAN_DB_POOL_RECYCLE_SECONDS", "86401"),
        )
        for name, value in invalid_values:
            with self.subTest(name=name, value=value):
                with patch.dict(os.environ, {name: value}, clear=False):
                    with self.assertRaisesRegex(ValueError, name):
                        create_database_engine(
                            "postgresql+psycopg://govoplan@example/govoplan",
                        )

    def test_collect_query_metrics_counts_instrumented_engine_queries(self) -> None:
        engine = create_database_engine("sqlite:///:memory:")
        try:
            with collect_query_metrics() as metrics:
                with engine.connect() as connection:
                    self.assertEqual(1, connection.execute(text("select 1")).scalar_one())

            self.assertEqual(1, metrics.query_count)
            self.assertGreaterEqual(metrics.total_ms, 0.0)
            self.assertGreaterEqual(metrics.slowest_ms, 0.0)
            self.assertEqual(0, metrics.error_count)
        finally:
            engine.dispose()

    def test_slow_request_log_includes_query_metrics(self) -> None:
        engine = create_database_engine("sqlite:///:memory:")
        router = APIRouter()

        @router.get("/query")
        def query_route() -> dict[str, bool]:
            with engine.connect() as connection:
                connection.execute(text("select 1")).scalar_one()
            return {"ok": True}

        try:
            with patch.dict(os.environ, {"GOVOPLAN_SLOW_REQUEST_MS": "0.001"}):
                app = create_govoplan_app(
                    title="query metrics test",
                    version="0",
                    registry=PlatformRegistry(),
                    api_router=router,
                )

            with TestClient(app) as client, self.assertLogs("govoplan.request", level="WARNING") as logs:
                response = client.get("/query")

            self.assertEqual(200, response.status_code, response.text)
            output = "\n".join(logs.output)
            self.assertIn("db_query_count=1", output)
            self.assertIn("db_time_ms=", output)
            self.assertIn("db_slowest_ms=", output)
            self.assertIn("db_error_count=0", output)
        finally:
            engine.dispose()
