"""API v2 metrics endpoints with caching."""

from uuid import UUID
from decimal import Decimal
from datetime import datetime
from typing import Optional

from fastapi import APIRouter, Depends, Query, Request, Header
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func

from src.api.deps import get_db
from src.api.v2.rate_limiter import TieredRateLimit
from src.repositories.scenario import scenario_repository
from src.schemas.metric import (
    MetricsResponse,
    MetricSummary,
    CostBreakdown,
    TimeseriesPoint,
)
from src.core.exceptions import NotFoundException
from src.core.config import settings
from src.core.cache import cache_manager
from src.core.monitoring import track_db_query, metrics as app_metrics
from src.services.cost_calculator import cost_calculator
from src.models.scenario_log import ScenarioLog


router = APIRouter()
rate_limiter = TieredRateLimit()


@router.get(
    "/{scenario_id}",
    response_model=MetricsResponse,
    summary="Get scenario metrics",
    description="Get aggregated metrics for a scenario with caching.",
)
async def get_scenario_metrics(
    request: Request,
    scenario_id: UUID,
    date_from: Optional[datetime] = Query(None, description="Start date filter"),
    date_to: Optional[datetime] = Query(None, description="End date filter"),
    force_refresh: bool = Query(False, description="Bypass cache"),
    db: AsyncSession = Depends(get_db),
    x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
):
    """Get aggregated metrics for a scenario.

    Results are cached for 5 minutes unless force_refresh is True.

    - **scenario_id**: Scenario UUID
    - **date_from**: Optional start date filter
    - **date_to**: Optional end date filter
    - **force_refresh**: Bypass cache and fetch fresh data
    """
    # Rate limiting
    await rate_limiter.check_rate_limit(request, x_api_key, tier="free")

    # Check cache
    cache_key = f"metrics:{scenario_id}:{date_from}:{date_to}"

    if not force_refresh:
        cached = await cache_manager.get(cache_key)
        if cached:
            app_metrics.track_cache_hit("l1")
            return MetricsResponse(**cached)

    app_metrics.track_cache_miss("l1")

    # Get scenario
    scenario = await scenario_repository.get(db, scenario_id)
    if not scenario:
        raise NotFoundException("Scenario")

    # Build query
    query = select(
        func.count(ScenarioLog.id).label("total_logs"),
        func.sum(ScenarioLog.sqs_blocks).label("total_sqs_blocks"),
        func.sum(ScenarioLog.token_count).label("total_tokens"),
        func.count(ScenarioLog.id)
        .filter(ScenarioLog.has_pii == True)
        .label("pii_violations"),
    ).where(ScenarioLog.scenario_id == scenario_id)

    if date_from:
        query = query.where(ScenarioLog.received_at >= date_from)
    if date_to:
        query = query.where(ScenarioLog.received_at <= date_to)

    # Execute query
    start_time = datetime.utcnow()
    result = await db.execute(query)
    row = result.one()
    duration = (datetime.utcnow() - start_time).total_seconds()
    track_db_query("SELECT", "scenario_logs", duration)

    # Calculate costs
    region = scenario.region
    sqs_cost = await cost_calculator.calculate_sqs_cost(
        db, row.total_sqs_blocks or 0, region
    )

    lambda_invocations = (row.total_logs or 0) // 100 + 1
    lambda_cost = await cost_calculator.calculate_lambda_cost(
        db, lambda_invocations, 1.0, region
    )

    bedrock_cost = await cost_calculator.calculate_bedrock_cost(
        db, row.total_tokens or 0, 0, region
    )

    total_cost = sqs_cost + lambda_cost + bedrock_cost

    cost_breakdown = [
        CostBreakdown(
            service="SQS",
            cost_usd=sqs_cost,
            percentage=float(sqs_cost / total_cost * 100) if total_cost > 0 else 0,
        ),
        CostBreakdown(
            service="Lambda",
            cost_usd=lambda_cost,
            percentage=float(lambda_cost / total_cost * 100) if total_cost > 0 else 0,
        ),
        CostBreakdown(
            service="Bedrock",
            cost_usd=bedrock_cost,
            percentage=float(bedrock_cost / total_cost * 100) if total_cost > 0 else 0,
        ),
    ]

    summary = MetricSummary(
        total_requests=scenario.total_requests,
        total_cost_usd=total_cost,
        sqs_blocks=row.total_sqs_blocks or 0,
        lambda_invocations=lambda_invocations,
        llm_tokens=row.total_tokens or 0,
        pii_violations=row.pii_violations or 0,
    )

    # Get timeseries data
    timeseries_query = (
        select(
            func.date_trunc("hour", ScenarioLog.received_at).label("hour"),
            func.count(ScenarioLog.id).label("count"),
        )
        .where(ScenarioLog.scenario_id == scenario_id)
        .group_by(func.date_trunc("hour", ScenarioLog.received_at))
        .order_by(func.date_trunc("hour", ScenarioLog.received_at))
    )

    if date_from:
        timeseries_query = timeseries_query.where(ScenarioLog.received_at >= date_from)
    if date_to:
        timeseries_query = timeseries_query.where(ScenarioLog.received_at <= date_to)

    start_time = datetime.utcnow()
    timeseries_result = await db.execute(timeseries_query)
    duration = (datetime.utcnow() - start_time).total_seconds()
    track_db_query("SELECT", "scenario_logs", duration)

    timeseries = [
        TimeseriesPoint(
            timestamp=row.hour,
            metric_type="requests",
            value=Decimal(row.count),
        )
        for row in timeseries_result.all()
    ]

    response = MetricsResponse(
        scenario_id=scenario_id,
        summary=summary,
        cost_breakdown=cost_breakdown,
        timeseries=timeseries,
    )

    # Cache result
    await cache_manager.set(
        cache_key,
        response.model_dump(),
        ttl=cache_manager.TTL_L1_QUERIES,
    )

    return response


@router.get(
    "/{scenario_id}/summary",
    summary="Get metrics summary",
    description="Get a lightweight metrics summary for a scenario.",
)
async def get_metrics_summary(
    request: Request,
    scenario_id: UUID,
    db: AsyncSession = Depends(get_db),
    x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
):
    """Get a lightweight metrics summary.

    Returns only essential metrics for quick display.
    """
    # Rate limiting (higher limit for lightweight endpoint)
    await rate_limiter.check_rate_limit(request, x_api_key, tier="free", burst=100)

    # Check cache
    cache_key = f"metrics:summary:{scenario_id}"
    cached = await cache_manager.get(cache_key)

    if cached:
        app_metrics.track_cache_hit("l1")
        return cached

    app_metrics.track_cache_miss("l1")

    scenario = await scenario_repository.get(db, scenario_id)
    if not scenario:
        raise NotFoundException("Scenario")

    result = await db.execute(
        select(
            func.count(ScenarioLog.id).label("total_logs"),
            func.sum(ScenarioLog.token_count).label("total_tokens"),
            func.count(ScenarioLog.id)
            .filter(ScenarioLog.has_pii == True)
            .label("pii_violations"),
        ).where(ScenarioLog.scenario_id == scenario_id)
    )
    row = result.one()

    summary = {
        "scenario_id": str(scenario_id),
        "total_logs": row.total_logs or 0,
        "total_tokens": row.total_tokens or 0,
        "pii_violations": row.pii_violations or 0,
        "total_requests": scenario.total_requests,
        "region": scenario.region,
        "status": scenario.status,
    }

    # Cache for longer (summary is less likely to change frequently)
    await cache_manager.set(cache_key, summary, ttl=cache_manager.TTL_L1_QUERIES * 2)

    return summary
