"""Monitoring and observability endpoints."""

from __future__ import annotations

from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
from urllib.parse import urlsplit, urlunsplit
from uuid import uuid4

import sentry_sdk
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field, field_validator
from sentry_sdk.transport import Transport
from sqlalchemy import text
from sqlalchemy.engine import make_url

from ..config import settings
from ..database import get_session
from ..models import AdminConfig
from ..security import require_roles
from ..services import metrics_collector
from ..utils import log_action

router = APIRouter()

MONITORING_CONFIG_KEY = "monitoring_settings"
_sentry_initialized: Optional[tuple[str, str]] = None


class MonitoringConfig(BaseModel):
    sentry_dsn: Optional[str] = Field(default=None)
    sentry_environment: str = Field(default="production")
    prometheus_enabled: bool = Field(default=False)
    prometheus_scrape_interval: int = Field(default=60, ge=15)
    last_ping: Optional[str] = None
    sentry_events: list[dict[str, str]] = Field(default_factory=list)

    @field_validator("sentry_dsn")
    @classmethod
    def validate_dsn(cls, value: Optional[str]):
        if value and not value.startswith("https://"):
            raise ValueError("Sentry DSN doit etre en HTTPS")
        return value


class SystemMetrics(BaseModel):
    requests_total: int
    active_connections: int
    average_response_time_ms: float
    error_rate_percent: float
    uptime_seconds: float
    database_size_mb: float
    last_scan_at: str


class MonitoringHealth(BaseModel):
    status: str
    database: str
    timestamp: str
    uptime_seconds: float


class SentryTestPayload(BaseModel):
    message: str = Field(default="Test observabilite")


class _NoopTransport(Transport):
    """Local transport preventing network calls."""

    def __init__(self, options):
        super().__init__(options)

    def capture_event(self, event):
        return event.get("event_id") or uuid4().hex

    def capture_envelope(self, envelope):
        return uuid4().hex


def _load_config(session) -> MonitoringConfig:
    config = session.query(AdminConfig).filter(AdminConfig.key == MONITORING_CONFIG_KEY).first()
    if config and config.value:
        return MonitoringConfig(**config.value)
    return MonitoringConfig()


def _save_config(session, data: MonitoringConfig) -> MonitoringConfig:
    record = session.query(AdminConfig).filter(AdminConfig.key == MONITORING_CONFIG_KEY).first()
    payload = data.model_dump()
    if record:
        record.value = payload
    else:
        record = AdminConfig(key=MONITORING_CONFIG_KEY, value=payload)
        session.add(record)
    return data


def _normalize_dsn(dsn: str) -> str:
    parts = urlsplit(dsn)
    if parts.username:
        return dsn
    netloc = parts.netloc
    if "@" not in netloc:
        netloc = f"public@{netloc}"
    normalized = parts._replace(netloc=netloc)
    return urlunsplit(normalized)


def _calculate_database_size_mb(session) -> float:
    url = make_url(settings.database_url)
    if url.get_backend_name().startswith("sqlite"):
        database = url.database
        if database and database != ":memory:":
            try:
                size = Path(database).stat().st_size
            except FileNotFoundError:
                return 0.0
            return round(size / (1024 * 1024), 2)
        return 0.0

    try:
        result = session.execute(text("SELECT pg_database_size(current_database())")).scalar()
    except Exception:
        return 0.0
    if result:
        return round(float(result) / (1024 * 1024), 2)
    return 0.0


def _ensure_sentry(config: MonitoringConfig) -> None:
    global _sentry_initialized
    if not config.sentry_dsn:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Sentry non configure")

    normalized_dsn = _normalize_dsn(config.sentry_dsn)
    key = (normalized_dsn, config.sentry_environment)
    if _sentry_initialized == key:
        return

    sentry_sdk.init(
        dsn=normalized_dsn,
        environment=config.sentry_environment,
        traces_sample_rate=0.0,
        transport=_NoopTransport,
    )
    config.sentry_dsn = normalized_dsn
    _sentry_initialized = key


@router.get("/config", response_model=MonitoringConfig)
def get_monitoring_config(session=Depends(get_session), _admin=Depends(require_roles("super-admin", "analyst"))):
    return _load_config(session)


@router.put("/config", response_model=MonitoringConfig)
def update_monitoring_config(
    payload: MonitoringConfig,
    session=Depends(get_session),
    _admin=Depends(require_roles("super-admin")),
):
    config = _save_config(session, payload)
    log_action(session, _admin.email, "monitoring_config_updated", metadata=payload.model_dump())
    session.commit()
    return config


@router.post("/sentry/test", response_model=MonitoringConfig)
def trigger_sentry_test(
    payload: SentryTestPayload,
    session=Depends(get_session),
    _admin=Depends(require_roles("super-admin")),
):
    config = _load_config(session)
    _ensure_sentry(config)
    try:
        event_id = sentry_sdk.capture_message(payload.message, level="info")
    except Exception as exc:  # pragma: no cover - depends on external service
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Impossible d'envoyer le message a Sentry: {exc}",
        ) from exc

    now = datetime.now(timezone.utc).isoformat()
    config.last_ping = now
    config.sentry_events = (config.sentry_events or [])[-9:] + [{"event_id": str(event_id), "created_at": now}]
    log_action(
        session,
        _admin.email,
        "monitoring_sentry_test",
        metadata={"message": payload.message, "environment": config.sentry_environment, "event_id": str(event_id)},
    )
    _save_config(session, config)
    session.commit()
    return config


@router.get("/metrics", response_model=SystemMetrics)
def get_system_metrics(session=Depends(get_session), _admin=Depends(require_roles("super-admin", "analyst"))):
    snapshot = metrics_collector.snapshot()
    return SystemMetrics(
        requests_total=int(snapshot["requests_total"]),
        active_connections=int(snapshot["active_requests"]),
        average_response_time_ms=round(float(snapshot["average_response_time_ms"]), 2),
        error_rate_percent=round(float(snapshot["error_rate_percent"]), 2),
        uptime_seconds=round(float(snapshot["uptime_seconds"]), 2),
        database_size_mb=_calculate_database_size_mb(session),
        last_scan_at=datetime.now(timezone.utc).isoformat(),
    )


@router.get("/health", response_model=MonitoringHealth)
def get_monitoring_health(session=Depends(get_session), _admin=Depends(require_roles("super-admin", "analyst"))):
    status_value = "ok"
    database_status = "ok"
    try:
        session.execute(text("SELECT 1"))
    except Exception:
        status_value = "degraded"
        database_status = "error"

    snapshot = metrics_collector.snapshot()
    return MonitoringHealth(
        status=status_value,
        database=database_status,
        timestamp=datetime.now(timezone.utc).isoformat(),
        uptime_seconds=round(float(snapshot["uptime_seconds"]), 2),
    )
