"""Database monitoring tools (read-only visibility).

These tools provide read-only visibility into the local SQLite database:
- get_database_stats: Monitor database size, sync status, and storage info
- get_problematic_tests: Get tests that failed to sync (500 errors)
- get_sync_history: View recent sync events for observability

IMPORTANT: Database sync operations (refresh, rebuild, clear) are available via CLI only.
Use `testio-mcp sync` commands for admin operations.

Tools are auto-discovered and registered via ADR-011 pattern.

Reference: STORY-021 AC7 (Database Management Tools)
"""

from datetime import UTC, datetime
from typing import Any, cast

from fastmcp import Context
from pydantic import BaseModel, Field

from testio_mcp.server import ServerContext, mcp
from testio_mcp.utilities.schema_utils import inline_schema_refs


def format_relative_time(timestamp_str: str | None) -> str | None:
    """Format timestamp as relative time (e.g., '5 minutes ago', '2 hours ago').

    Args:
        timestamp_str: ISO 8601 timestamp string (e.g., '2025-11-19T10:30:00+00:00')

    Returns:
        Human-readable relative time string, or None if timestamp is None
    """
    if timestamp_str is None:
        return None

    try:
        # Parse timestamp (handles both UTC and timezone-aware)
        timestamp = datetime.fromisoformat(timestamp_str)
        if timestamp.tzinfo is None:
            # Assume UTC if naive
            timestamp = timestamp.replace(tzinfo=UTC)

        # Calculate time difference
        now = datetime.now(UTC)
        delta = now - timestamp

        # Format relative time
        seconds = int(delta.total_seconds())
        if seconds < 0:
            return "just now"
        elif seconds < 60:
            return f"{seconds} seconds ago"
        elif seconds < 3600:
            minutes = seconds // 60
            return f"{minutes} minute{'s' if minutes != 1 else ''} ago"
        elif seconds < 86400:
            hours = seconds // 3600
            return f"{hours} hour{'s' if hours != 1 else ''} ago"
        else:
            days = seconds // 86400
            return f"{days} day{'s' if days != 1 else ''} ago"
    except (ValueError, TypeError):
        return None


# Pydantic Output Models (for REST API + Swagger)
# NOTE: Models use nested BaseModel classes for type safety and better FastAPI docs.
# Schemas are post-processed with inline_schema_refs() to avoid $ref resolution issues
# in some MCP clients like Gemini CLI 0.16.0.


class StorageInfo(BaseModel):
    """Database storage metadata."""

    oldest_test_date: str | None = Field(
        default=None, description="Oldest test end date in database"
    )
    newest_test_date: str | None = Field(
        default=None, description="Newest test end date in database"
    )


class DatabaseStatsOutput(BaseModel):
    """Database statistics and sync status output."""

    database_size_mb: float = Field(description="Database file size in megabytes")
    database_path: str = Field(description="Full path to SQLite database file")
    total_tests: int = Field(description="Total number of tests in database", ge=0)
    total_products: int = Field(description="Total number of products in database", ge=0)
    products_synced: list[dict[str, Any]] = Field(
        description="List of synced products with sync metadata"
    )
    storage_info: StorageInfo = Field(description="Date range of stored test data")


class ProblematicTestsOutput(BaseModel):
    """Problematic tests (failed to sync) output."""

    count: int = Field(description="Number of problematic tests", ge=0)
    tests: list[dict[str, Any]] = Field(description="List of tests with boundary information")
    message: str = Field(description="Guidance message for using this data")


class SyncSummary(BaseModel):
    """Aggregate statistics for sync events."""

    total_events: int = Field(description="Total number of sync events", ge=0)
    completed: int = Field(description="Number of completed syncs", ge=0)
    failed: int = Field(description="Number of failed syncs", ge=0)
    running: int = Field(description="Number of currently running syncs", ge=0)
    success_rate: float = Field(description="Success rate percentage", ge=0, le=100)
    avg_duration_seconds: float = Field(description="Average duration in seconds", ge=0)


class CircuitBreakerStatus(BaseModel):
    """Circuit breaker status for restart loop protection."""

    recent_failures_5min: int = Field(
        description="Number of failures in last 5 minutes",
        ge=0,
    )
    is_active: bool = Field(description="Whether circuit breaker is active (3+ failures)")
    message: str = Field(description="Status message describing circuit breaker state")


class SyncHistoryOutput(BaseModel):
    """Sync event history with statistics output."""

    events: list[dict[str, Any]] = Field(description="List of sync events (newest first)")
    summary: SyncSummary = Field(description="Aggregate statistics")
    circuit_breaker: CircuitBreakerStatus = Field(description="Circuit breaker status")


@mcp.tool(output_schema=inline_schema_refs(DatabaseStatsOutput.model_json_schema()))
async def get_database_stats(ctx: Context) -> dict[str, Any]:
    """Get local database statistics and sync status.

    Shows database size, test counts, last sync times, and storage info.
    Useful for monitoring data freshness and disk usage.

    Returns:
        Dict with database_size_mb, database_path, total_tests, total_products,
        products_synced, and storage_info (oldest/newest test dates)
    """
    # Access cache from lifespan context (ADR-007)
    assert ctx.request_context is not None  # Always available in MCP tool context
    lifespan_ctx = cast(ServerContext, ctx.request_context.lifespan_context)
    cache = lifespan_ctx["cache"]

    # Get products info and add relative time
    products_synced = await cache.get_synced_products_info()
    for product in products_synced:
        product["last_synced_relative"] = format_relative_time(product.get("last_synced"))

    # Build result with validated output model (nested StorageInfo)
    result = DatabaseStatsOutput(
        database_size_mb=await cache.get_db_size_mb(),
        database_path=str(cache.db_path),
        total_tests=await cache.count_tests(),
        total_products=await cache.count_products(),
        products_synced=products_synced,
        storage_info=StorageInfo(
            oldest_test_date=await cache.get_oldest_test_date(),
            newest_test_date=await cache.get_newest_test_date(),
        ),
    )
    return result.model_dump(by_alias=True, exclude_none=True)


@mcp.tool(output_schema=ProblematicTestsOutput.model_json_schema())
async def get_problematic_tests(ctx: Context, product_id: int | None = None) -> dict[str, Any]:
    """Get tests that failed to sync due to API 500 errors.

    Args:
        ctx: FastMCP context
        product_id: Optional filter for specific product (for EBR integration)

    Returns:
        Dict with count and list of problematic tests with boundary information.
        Boundary IDs help identify the missing test in the UI.
        Useful for filing bug reports with TestIO support and for including
        in EBR reports (Epic 003).
    """
    # Access cache from lifespan context (ADR-007)
    assert ctx.request_context is not None  # Always available in MCP tool context
    lifespan_ctx = cast(ServerContext, ctx.request_context.lifespan_context)
    cache = lifespan_ctx["cache"]

    # Get problematic tests (with optional product filter)
    problematic = await cache.get_problematic_tests(product_id=product_id)

    # Build result with validated output model
    result = ProblematicTestsOutput(
        count=len(problematic),
        tests=problematic,
        message=(
            "These tests encountered 500 errors during sync. "
            "Use boundary IDs to identify them in the UI."
        ),
    )
    return result.model_dump(by_alias=True, exclude_none=True)


@mcp.tool(output_schema=inline_schema_refs(SyncHistoryOutput.model_json_schema()))
async def get_sync_history(ctx: Context, limit: int = 10) -> dict[str, Any]:
    """Get recent sync event history with success/failure statistics.

    Shows sync operations (initial sync, background refresh) with timing,
    statistics, and error information. Useful for debugging sync issues,
    monitoring performance, and detecting restart loops.

    Args:
        ctx: FastMCP context
        limit: Maximum number of events to return (default: 10, max: 50)

    Returns:
        Dict with:
        - events: List of sync events (newest first) with timestamps, status, statistics
        - summary: Aggregate statistics (total events, success rate, avg duration)
        - circuit_breaker: Circuit breaker status (recent failures, is active)
    """
    # Access cache from lifespan context (ADR-007)
    assert ctx.request_context is not None  # Always available in MCP tool context
    lifespan_ctx = cast(ServerContext, ctx.request_context.lifespan_context)
    cache = lifespan_ctx["cache"]

    # Validate and clamp limit
    limit = max(1, min(limit, 50))

    # Get sync events
    events = await cache.get_sync_events(limit=limit)

    # Calculate summary statistics
    total_events = len(events)
    completed = sum(1 for e in events if e["status"] == "completed")
    failed = sum(1 for e in events if e["status"] == "failed")
    running = sum(1 for e in events if e["status"] == "running")

    success_rate = round((completed / total_events * 100), 1) if total_events > 0 else 0.0

    # Calculate average duration for completed events
    completed_durations = [
        e["duration_seconds"] for e in events if e["duration_seconds"] is not None
    ]
    avg_duration = (
        round(sum(completed_durations) / len(completed_durations), 2)
        if completed_durations
        else 0.0
    )

    # Check circuit breaker status
    from datetime import UTC, datetime, timedelta

    recent_failures = await cache.count_sync_failures_since(
        datetime.now(UTC) - timedelta(minutes=5)
    )
    circuit_breaker_active = recent_failures >= 3

    # Build result with validated output model (nested models)
    result = SyncHistoryOutput(
        events=events,
        summary=SyncSummary(
            total_events=total_events,
            completed=completed,
            failed=failed,
            running=running,
            success_rate=success_rate,
            avg_duration_seconds=avg_duration,
        ),
        circuit_breaker=CircuitBreakerStatus(
            recent_failures_5min=recent_failures,
            is_active=circuit_breaker_active,
            message=(
                "🛑 Circuit breaker active! Syncs are blocked due to repeated failures. "
                "Check error messages in events and fix issues before retrying."
                if circuit_breaker_active
                else "✅ Circuit breaker inactive. Syncs are running normally."
            ),
        ),
    )
    return result.model_dump(by_alias=True, exclude_none=True)
