from __future__ import annotations

from datetime import date

from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.responses import HTMLResponse

from dependencies import (
    get_unit_of_work,
    require_any_role,
    require_any_role_html,
    require_project_resource,
    require_scenario_resource,
    require_project_resource_html,
    require_scenario_resource_html,
)
from models import Project, Scenario, User
from services.exceptions import EntityNotFoundError, ScenarioValidationError
from services.reporting import (
    DEFAULT_ITERATIONS,
    IncludeOptions,
    ReportFilters,
    ReportingService,
    parse_include_tokens,
    validate_percentiles,
)
from services.unit_of_work import UnitOfWork
from routes.template_filters import create_templates

router = APIRouter(prefix="/reports", tags=["Reports"])
templates = create_templates()

READ_ROLES = ("viewer", "analyst", "project_manager", "admin")
MANAGE_ROLES = ("project_manager", "admin")


@router.get("/projects/{project_id}", name="reports.project_summary")
def project_summary_report(
    project: Project = Depends(require_project_resource()),
    _: User = Depends(require_any_role(*READ_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
    include: str | None = Query(
        None,
        description="Comma-separated include tokens (distribution,samples,all).",
    ),
    scenario_ids: list[int] | None = Query(
        None,
        alias="scenario_ids",
        description="Repeatable scenario identifier filter.",
    ),
    start_date: date | None = Query(
        None,
        description="Filter scenarios starting on or after this date.",
    ),
    end_date: date | None = Query(
        None,
        description="Filter scenarios ending on or before this date.",
    ),
    fmt: str = Query(
        "json",
        alias="format",
        description="Response format (json only for this endpoint).",
    ),
    iterations: int | None = Query(
        None,
        gt=0,
        description="Override Monte Carlo iteration count when distribution is included.",
    ),
    percentiles: list[float] | None = Query(
        None,
        description="Percentiles (0-100) for Monte Carlo summaries when included.",
    ),
) -> dict[str, object]:
    if fmt.lower() != "json":
        raise HTTPException(
            status_code=status.HTTP_406_NOT_ACCEPTABLE,
            detail="Only JSON responses are supported; use the HTML endpoint for templates.",
        )

    include_options = parse_include_tokens(include)
    try:
        percentile_values = validate_percentiles(percentiles)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc

    scenario_filter = ReportFilters(
        scenario_ids=set(scenario_ids) if scenario_ids else None,
        start_date=start_date,
        end_date=end_date,
    )

    service = ReportingService(uow)
    report = service.project_summary(
        project,
        filters=scenario_filter,
        include=include_options,
        iterations=iterations or DEFAULT_ITERATIONS,
        percentiles=percentile_values,
    )
    return jsonable_encoder(report)


@router.get(
    "/projects/{project_id}/scenarios/compare",
    name="reports.project_scenario_comparison",
)
def project_scenario_comparison_report(
    project: Project = Depends(require_project_resource()),
    _: User = Depends(require_any_role(*READ_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
    scenario_ids: list[int] = Query(
        ..., alias="scenario_ids", description="Repeatable scenario identifier."),
    include: str | None = Query(
        None,
        description="Comma-separated include tokens (distribution,samples,all).",
    ),
    fmt: str = Query(
        "json",
        alias="format",
        description="Response format (json only for this endpoint).",
    ),
    iterations: int | None = Query(
        None,
        gt=0,
        description="Override Monte Carlo iteration count when distribution is included.",
    ),
    percentiles: list[float] | None = Query(
        None,
        description="Percentiles (0-100) for Monte Carlo summaries when included.",
    ),
) -> dict[str, object]:
    unique_ids = list(dict.fromkeys(scenario_ids))
    if len(unique_ids) < 2:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail="At least two unique scenario_ids must be provided for comparison.",
        )
    if fmt.lower() != "json":
        raise HTTPException(
            status_code=status.HTTP_406_NOT_ACCEPTABLE,
            detail="Only JSON responses are supported; use the HTML endpoint for templates.",
        )

    include_options = parse_include_tokens(include)
    try:
        percentile_values = validate_percentiles(percentiles)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc

    try:
        scenarios = uow.validate_scenarios_for_comparison(unique_ids)
    except ScenarioValidationError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail={
                "code": exc.code,
                "message": exc.message,
                "scenario_ids": list(exc.scenario_ids or []),
            },
        ) from exc
    except EntityNotFoundError as exc:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=str(exc),
        ) from exc

    if any(scenario.project_id != project.id for scenario in scenarios):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="One or more scenarios are not associated with this project.",
        )

    service = ReportingService(uow)
    report = service.scenario_comparison(
        project,
        scenarios,
        include=include_options,
        iterations=iterations or DEFAULT_ITERATIONS,
        percentiles=percentile_values,
    )
    return jsonable_encoder(report)


@router.get(
    "/scenarios/{scenario_id}/distribution",
    name="reports.scenario_distribution",
)
def scenario_distribution_report(
    scenario: Scenario = Depends(require_scenario_resource()),
    _: User = Depends(require_any_role(*READ_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
    include: str | None = Query(
        None,
        description="Comma-separated include tokens (samples,all).",
    ),
    fmt: str = Query(
        "json",
        alias="format",
        description="Response format (json only for this endpoint).",
    ),
    iterations: int | None = Query(
        None,
        gt=0,
        description="Override Monte Carlo iteration count (default applies otherwise).",
    ),
    percentiles: list[float] | None = Query(
        None,
        description="Percentiles (0-100) for Monte Carlo summaries.",
    ),
) -> dict[str, object]:
    if fmt.lower() != "json":
        raise HTTPException(
            status_code=status.HTTP_406_NOT_ACCEPTABLE,
            detail="Only JSON responses are supported; use the HTML endpoint for templates.",
        )

    requested = parse_include_tokens(include)
    include_options = IncludeOptions(
        distribution=True, samples=requested.samples)

    try:
        percentile_values = validate_percentiles(percentiles)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc

    service = ReportingService(uow)
    report = service.scenario_distribution(
        scenario,
        include=include_options,
        iterations=iterations or DEFAULT_ITERATIONS,
        percentiles=percentile_values,
    )
    return jsonable_encoder(report)


@router.get(
    "/projects/{project_id}/ui",
    response_class=HTMLResponse,
    include_in_schema=False,
    name="reports.project_summary_page",
)
def project_summary_page(
    request: Request,
    project: Project = Depends(require_project_resource_html()),
    _: User = Depends(require_any_role_html(*READ_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
    include: str | None = Query(
        None,
        description="Comma-separated include tokens (distribution,samples,all).",
    ),
    scenario_ids: list[int] | None = Query(
        None,
        alias="scenario_ids",
        description="Repeatable scenario identifier filter.",
    ),
    start_date: date | None = Query(
        None,
        description="Filter scenarios starting on or after this date.",
    ),
    end_date: date | None = Query(
        None,
        description="Filter scenarios ending on or before this date.",
    ),
    iterations: int | None = Query(
        None,
        gt=0,
        description="Override Monte Carlo iteration count when distribution is included.",
    ),
    percentiles: list[float] | None = Query(
        None,
        description="Percentiles (0-100) for Monte Carlo summaries when included.",
    ),
) -> HTMLResponse:
    include_options = parse_include_tokens(include)
    try:
        percentile_values = validate_percentiles(percentiles)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc

    scenario_filter = ReportFilters(
        scenario_ids=set(scenario_ids) if scenario_ids else None,
        start_date=start_date,
        end_date=end_date,
    )

    service = ReportingService(uow)
    context = service.build_project_summary_context(
        project, scenario_filter, include_options, iterations or DEFAULT_ITERATIONS, percentile_values, request
    )
    return templates.TemplateResponse(
        request,
        "reports/project_summary.html",
        context,
    )


@router.get(
    "/projects/{project_id}/scenarios/compare/ui",
    response_class=HTMLResponse,
    include_in_schema=False,
    name="reports.project_scenario_comparison_page",
)
def project_scenario_comparison_page(
    request: Request,
    project: Project = Depends(require_project_resource_html()),
    _: User = Depends(require_any_role_html(*READ_ROLES)),
    uow: UnitOfWork = Depends(get_unit_of_work),
    scenario_ids: list[int] = Query(
        ..., alias="scenario_ids", description="Repeatable scenario identifier."),
    include: str | None = Query(
        None,
        description="Comma-separated include tokens (distribution,samples,all).",
    ),
    iterations: int | None = Query(
        None,
        gt=0,
        description="Override Monte Carlo iteration count when distribution is included.",
    ),
    percentiles: list[float] | None = Query(
        None,
        description="Percentiles (0-100) for Monte Carlo summaries when included.",
    ),
) -> HTMLResponse:
    unique_ids = list(dict.fromkeys(scenario_ids))
    if len(unique_ids) < 2:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail="At least two unique scenario_ids must be provided for comparison.",
        )

    include_options = parse_include_tokens(include)
    try:
        percentile_values = validate_percentiles(percentiles)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc

    try:
        scenarios = uow.validate_scenarios_for_comparison(unique_ids)
    except ScenarioValidationError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail={
                "code": exc.code,
                "message": exc.message,
                "scenario_ids": list(exc.scenario_ids or []),
            },
        ) from exc
    except EntityNotFoundError as exc:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=str(exc),
        ) from exc

    if any(scenario.project_id != project.id for scenario in scenarios):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="One or more scenarios are not associated with this project.",
        )

    service = ReportingService(uow)
    context = service.build_scenario_comparison_context(
        project, scenarios, include_options, iterations or DEFAULT_ITERATIONS, percentile_values, request
    )
    return templates.TemplateResponse(
        request,
        "reports/scenario_comparison.html",
        context,
    )


@router.get(
    "/scenarios/{scenario_id}/distribution/ui",
    response_class=HTMLResponse,
    include_in_schema=False,
    name="reports.scenario_distribution_page",
)
def scenario_distribution_page(
    request: Request,
    _: User = Depends(require_any_role_html(*READ_ROLES)),
    scenario: Scenario = Depends(
        require_scenario_resource_html()
    ),
    uow: UnitOfWork = Depends(get_unit_of_work),
    include: str | None = Query(
        None,
        description="Comma-separated include tokens (samples,all).",
    ),
    iterations: int | None = Query(
        None,
        gt=0,
        description="Override Monte Carlo iteration count (default applies otherwise).",
    ),
    percentiles: list[float] | None = Query(
        None,
        description="Percentiles (0-100) for Monte Carlo summaries.",
    ),
) -> HTMLResponse:
    requested = parse_include_tokens(include)
    include_options = IncludeOptions(
        distribution=True, samples=requested.samples)

    try:
        percentile_values = validate_percentiles(percentiles)
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc

    service = ReportingService(uow)
    context = service.build_scenario_distribution_context(
        scenario, include_options, iterations or DEFAULT_ITERATIONS, percentile_values, request
    )
    return templates.TemplateResponse(
        request,
        "reports/scenario_distribution.html",
        context,
    )
