"""
Route handlers for pipeline run history and dashboard analytics.

Provides endpoints for querying pipeline runs, aggregated statistics,
and timeseries data used by the pipeline dashboard.
"""

from __future__ import annotations

import logging
from collections import defaultdict
from datetime import datetime, timedelta, timezone

from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func as sa_func
from sqlalchemy.orm import Session

from pycharter.api.dependencies.database import get_db_session
from pycharter.api.models.runs import (
    PipelineListResponse,
    PipelineRunListResponse,
    PipelineRunResponse,
    PipelineSummary,
    RunStatsResponse,
    RunTimeseriesResponse,
    TimeseriesBucket,
)
from pycharter.db.models.pipeline_run import PipelineRunModel

logger = logging.getLogger(__name__)

router = APIRouter(tags=["Pipeline Runs"])


def _model_to_response(run: PipelineRunModel) -> PipelineRunResponse:
    """Convert a PipelineRunModel to a PipelineRunResponse."""
    return PipelineRunResponse(
        id=str(run.id),
        run_id=run.run_id,
        pipeline_name=run.pipeline_name,
        status=run.status,
        rows_extracted=run.rows_extracted or 0,
        rows_transformed=run.rows_transformed or 0,
        rows_loaded=run.rows_loaded or 0,
        rows_failed=run.rows_failed or 0,
        rows_quarantined=run.rows_quarantined or 0,
        batches_processed=run.batches_processed or 0,
        started_at=run.started_at,
        completed_at=run.completed_at,
        duration_seconds=run.duration_seconds,
        contract_quality_score=run.contract_quality_score,
        contract_quality_passed=run.contract_quality_passed,
        pipeline_quality_passed=run.pipeline_quality_passed,
        extractor_type=run.extractor_type,
        loader_type=run.loader_type,
        trigger=run.trigger or "api",
        errors=run.errors,
        created_at=run.created_at,
    )


@router.get(
    "/runs",
    response_model=PipelineRunListResponse,
    status_code=status.HTTP_200_OK,
    summary="List pipeline runs",
    description="List pipeline runs with optional filters and pagination.",
)
async def list_runs(
    pipeline: str | None = Query(None, description="Filter by pipeline name"),
    run_status: str | None = Query(
        None, alias="status", description="Filter by status"
    ),
    from_date: datetime | None = Query(
        None, alias="from", description="Start date filter (ISO 8601)"
    ),
    to_date: datetime | None = Query(
        None, alias="to", description="End date filter (ISO 8601)"
    ),
    limit: int = Query(50, ge=1, le=500, description="Page size"),
    offset: int = Query(0, ge=0, description="Page offset"),
    db: Session = Depends(get_db_session),
) -> PipelineRunListResponse:
    """List pipeline runs with filters and pagination."""
    query = db.query(PipelineRunModel)

    if pipeline:
        query = query.filter(PipelineRunModel.pipeline_name == pipeline)
    if run_status:
        query = query.filter(PipelineRunModel.status == run_status)
    if from_date:
        query = query.filter(PipelineRunModel.started_at >= from_date)
    if to_date:
        query = query.filter(PipelineRunModel.started_at <= to_date)

    total = query.count()
    runs = (
        query.order_by(PipelineRunModel.started_at.desc())
        .offset(offset)
        .limit(limit)
        .all()
    )

    return PipelineRunListResponse(
        runs=[_model_to_response(r) for r in runs],
        total=total,
        limit=limit,
        offset=offset,
    )


@router.get(
    "/runs/stats",
    response_model=RunStatsResponse,
    status_code=status.HTTP_200_OK,
    summary="Get run statistics",
    description="Get aggregated statistics for pipeline runs.",
)
async def get_run_stats(
    pipeline: str | None = Query(None, description="Filter by pipeline name"),
    from_date: datetime | None = Query(
        None, alias="from", description="Start date filter"
    ),
    to_date: datetime | None = Query(None, alias="to", description="End date filter"),
    db: Session = Depends(get_db_session),
) -> RunStatsResponse:
    """Get aggregated statistics for pipeline runs."""
    query = db.query(PipelineRunModel)

    if pipeline:
        query = query.filter(PipelineRunModel.pipeline_name == pipeline)
    if from_date:
        query = query.filter(PipelineRunModel.started_at >= from_date)
    if to_date:
        query = query.filter(PipelineRunModel.started_at <= to_date)

    runs = query.all()
    total = len(runs)

    if total == 0:
        return RunStatsResponse()

    successful = sum(1 for r in runs if r.status == "success")
    failed = sum(1 for r in runs if r.status == "failed")
    durations = [r.duration_seconds for r in runs if r.duration_seconds is not None]
    total_loaded = sum(r.rows_loaded or 0 for r in runs)
    total_failed_rows = sum(r.rows_failed or 0 for r in runs)

    return RunStatsResponse(
        total_runs=total,
        successful_runs=successful,
        failed_runs=failed,
        success_rate=round((successful / total) * 100, 1) if total > 0 else 0.0,
        avg_duration_seconds=(
            round(sum(durations) / len(durations), 2) if durations else None
        ),
        total_rows_loaded=total_loaded,
        total_rows_failed=total_failed_rows,
        avg_rows_per_run=round(total_loaded / total, 1) if total > 0 else 0.0,
    )


@router.get(
    "/runs/timeseries",
    response_model=RunTimeseriesResponse,
    status_code=status.HTTP_200_OK,
    summary="Get run timeseries",
    description="Get time-bucketed run data for dashboard charts.",
)
async def get_run_timeseries(
    granularity: str = Query("day", description="Bucket granularity: hour or day"),
    pipeline: str | None = Query(None, description="Filter by pipeline name"),
    from_date: datetime | None = Query(
        None, alias="from", description="Start date filter"
    ),
    to_date: datetime | None = Query(None, alias="to", description="End date filter"),
    db: Session = Depends(get_db_session),
) -> RunTimeseriesResponse:
    """Get time-bucketed pipeline run data for charts."""
    if granularity not in ("hour", "day"):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="granularity must be 'hour' or 'day'",
        )

    # Default date range: last 30 days for day, last 48 hours for hour
    now = datetime.now(timezone.utc)
    if not to_date:
        to_date = now
    if not from_date:
        from_date = (
            now - timedelta(days=30)
            if granularity == "day"
            else now - timedelta(hours=48)
        )

    query = db.query(PipelineRunModel).filter(
        PipelineRunModel.started_at >= from_date,
        PipelineRunModel.started_at <= to_date,
    )
    if pipeline:
        query = query.filter(PipelineRunModel.pipeline_name == pipeline)

    runs = query.all()

    # Bucket runs by time
    buckets: dict[str, list[PipelineRunModel]] = defaultdict(list)
    for run in runs:
        if run.started_at is None:
            continue
        ts = run.started_at
        if granularity == "day":
            key = ts.strftime("%Y-%m-%d")
        else:
            key = ts.strftime("%Y-%m-%dT%H:00:00")
        buckets[key].append(run)

    # Build response buckets in chronological order
    result_buckets = []
    for key in sorted(buckets.keys()):
        bucket_runs = buckets[key]
        durations = [
            r.duration_seconds for r in bucket_runs if r.duration_seconds is not None
        ]
        result_buckets.append(
            TimeseriesBucket(
                timestamp=key,
                total=len(bucket_runs),
                successful=sum(1 for r in bucket_runs if r.status == "success"),
                failed=sum(1 for r in bucket_runs if r.status == "failed"),
                avg_duration=(
                    round(sum(durations) / len(durations), 2) if durations else None
                ),
                rows_loaded=sum(r.rows_loaded or 0 for r in bucket_runs),
            )
        )

    return RunTimeseriesResponse(
        granularity=granularity,
        buckets=result_buckets,
    )


@router.get(
    "/runs/{run_id}",
    response_model=PipelineRunResponse,
    status_code=status.HTTP_200_OK,
    summary="Get pipeline run",
    description="Get details for a specific pipeline run.",
)
async def get_run(
    run_id: str,
    db: Session = Depends(get_db_session),
) -> PipelineRunResponse:
    """Get a specific pipeline run by run_id."""
    run = db.query(PipelineRunModel).filter(PipelineRunModel.run_id == run_id).first()
    if not run:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Pipeline run '{run_id}' not found",
        )
    return _model_to_response(run)


@router.get(
    "/pipelines",
    response_model=PipelineListResponse,
    status_code=status.HTTP_200_OK,
    summary="List pipelines",
    description="List distinct pipeline names with summary statistics.",
)
async def list_pipelines(
    db: Session = Depends(get_db_session),
) -> PipelineListResponse:
    """List distinct pipelines with summary stats."""
    # Get all runs grouped by pipeline
    runs = (
        db.query(PipelineRunModel)
        .filter(PipelineRunModel.pipeline_name.isnot(None))
        .order_by(PipelineRunModel.started_at.desc())
        .all()
    )

    pipeline_runs: dict[str, list[PipelineRunModel]] = defaultdict(list)
    for run in runs:
        pipeline_runs[run.pipeline_name].append(run)

    summaries = []
    for name, p_runs in sorted(pipeline_runs.items()):
        total = len(p_runs)
        successful = sum(1 for r in p_runs if r.status == "success")
        failed = sum(1 for r in p_runs if r.status == "failed")
        durations = [
            r.duration_seconds for r in p_runs if r.duration_seconds is not None
        ]
        latest = p_runs[0] if p_runs else None

        summaries.append(
            PipelineSummary(
                pipeline_name=name,
                total_runs=total,
                successful_runs=successful,
                failed_runs=failed,
                success_rate=round((successful / total) * 100, 1) if total else 0.0,
                last_run_at=latest.started_at if latest else None,
                last_status=latest.status if latest else None,
                avg_duration_seconds=(
                    round(sum(durations) / len(durations), 2) if durations else None
                ),
            )
        )

    return PipelineListResponse(pipelines=summaries)


@router.get(
    "/pipelines/{name}/stats",
    response_model=RunStatsResponse,
    status_code=status.HTTP_200_OK,
    summary="Get pipeline stats",
    description="Get aggregated statistics for a specific pipeline.",
)
async def get_pipeline_stats(
    name: str,
    db: Session = Depends(get_db_session),
) -> RunStatsResponse:
    """Get stats for a specific pipeline."""
    return await get_run_stats(pipeline=name, from_date=None, to_date=None, db=db)
