from __future__ import annotations

import logging
import time
from datetime import datetime, timezone
from typing import Annotated

from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from fastapi.responses import HTMLResponse, StreamingResponse

from dependencies import get_unit_of_work, require_any_role
from schemas.exports import (
    ExportFormat,
    ProjectExportRequest,
    ScenarioExportRequest,
)
from services.export_serializers import (
    export_projects_to_excel,
    export_scenarios_to_excel,
    stream_projects_to_csv,
    stream_scenarios_to_csv,
)
from services.unit_of_work import UnitOfWork
from models.import_export_log import ImportExportLog
from monitoring.metrics import observe_export
from routes.template_filters import create_templates

logger = logging.getLogger(__name__)

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


@router.get(
    "/modal/{dataset}",
    response_model=None,
    response_class=HTMLResponse,
    include_in_schema=False,
    name="exports.modal",
)
async def export_modal(
    dataset: str,
    request: Request,
) -> HTMLResponse:
    dataset = dataset.lower()
    if dataset not in {"projects", "scenarios"}:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Unknown dataset")

    submit_url = request.url_for(
        "export_projects" if dataset == "projects" else "export_scenarios"
    )
    return templates.TemplateResponse(
        request,
        "exports/modal.html",
        {
            "dataset": dataset,
            "submit_url": submit_url,
        },
    )


def _timestamp_suffix() -> str:
    return datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")


def _ensure_repository(repo, name: str):
    if repo is None:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                            detail=f"{name} repository unavailable")
    return repo


def _record_export_audit(
    *,
    uow: UnitOfWork,
    dataset: str,
    status: str,
    export_format: ExportFormat,
    row_count: int,
    filename: str | None,
) -> None:
    try:
        if uow.session is None:
            return
        log = ImportExportLog(
            action="export",
            dataset=dataset,
            status=status,
            filename=filename,
            row_count=row_count,
            detail=f"format={export_format.value}",
        )
        uow.session.add(log)
        uow.commit()
    except Exception:
        # best-effort auditing, do not break exports
        if uow.session is not None:
            uow.session.rollback()
        logger.exception(
            "export.audit.failed",
            extra={
                "event": "export.audit",
                "dataset": dataset,
                "status": status,
                "format": export_format.value,
            },
        )


@router.post(
    "/projects",
    status_code=status.HTTP_200_OK,
    response_class=StreamingResponse,
    dependencies=[Depends(require_any_role(
        "admin", "project_manager", "analyst"))],
)
async def export_projects(
    request: ProjectExportRequest,
    uow: Annotated[UnitOfWork, Depends(get_unit_of_work)],
) -> Response:
    project_repo = _ensure_repository(
        getattr(uow, "projects", None), "Project")
    start = time.perf_counter()
    try:
        projects = project_repo.filtered_for_export(request.filters)
    except ValueError as exc:
        _record_export_audit(
            uow=uow,
            dataset="projects",
            status="failure",
            export_format=request.format,
            row_count=0,
            filename=None,
        )
        logger.warning(
            "export.validation_failed",
            extra={
                "event": "export",
                "dataset": "projects",
                "status": "validation_failed",
                "format": request.format.value,
                "error": str(exc),
            },
        )
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        _record_export_audit(
            uow=uow,
            dataset="projects",
            status="failure",
            export_format=request.format,
            row_count=0,
            filename=None,
        )
        logger.exception(
            "export.failed",
            extra={
                "event": "export",
                "dataset": "projects",
                "status": "failure",
                "format": request.format.value,
            },
        )
        raise exc

    filename = f"projects-{_timestamp_suffix()}"

    if request.format == ExportFormat.CSV:
        stream = stream_projects_to_csv(projects)
        response = StreamingResponse(stream, media_type="text/csv")
        response.headers["Content-Disposition"] = f"attachment; filename={filename}.csv"
        _record_export_audit(
            uow=uow,
            dataset="projects",
            status="success",
            export_format=request.format,
            row_count=len(projects),
            filename=f"{filename}.csv",
        )
        logger.info(
            "export",
            extra={
                "event": "export",
                "dataset": "projects",
                "status": "success",
                "format": request.format.value,
                "row_count": len(projects),
                "filename": f"{filename}.csv",
            },
        )
        observe_export(
            dataset="projects",
            status="success",
            export_format=request.format.value,
            seconds=time.perf_counter() - start,
        )
        return response

    data = export_projects_to_excel(projects)
    _record_export_audit(
        uow=uow,
        dataset="projects",
        status="success",
        export_format=request.format,
        row_count=len(projects),
        filename=f"{filename}.xlsx",
    )
    logger.info(
        "export",
        extra={
            "event": "export",
            "dataset": "projects",
            "status": "success",
            "format": request.format.value,
            "row_count": len(projects),
            "filename": f"{filename}.xlsx",
        },
    )
    observe_export(
        dataset="projects",
        status="success",
        export_format=request.format.value,
        seconds=time.perf_counter() - start,
    )
    return StreamingResponse(
        iter([data]),
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        headers={
            "Content-Disposition": f"attachment; filename={filename}.xlsx",
        },
    )


@router.post(
    "/scenarios",
    status_code=status.HTTP_200_OK,
    response_class=StreamingResponse,
    dependencies=[Depends(require_any_role(
        "admin", "project_manager", "analyst"))],
)
async def export_scenarios(
    request: ScenarioExportRequest,
    uow: Annotated[UnitOfWork, Depends(get_unit_of_work)],
) -> Response:
    scenario_repo = _ensure_repository(
        getattr(uow, "scenarios", None), "Scenario")
    start = time.perf_counter()
    try:
        scenarios = scenario_repo.filtered_for_export(
            request.filters, include_project=True)
    except ValueError as exc:
        _record_export_audit(
            uow=uow,
            dataset="scenarios",
            status="failure",
            export_format=request.format,
            row_count=0,
            filename=None,
        )
        logger.warning(
            "export.validation_failed",
            extra={
                "event": "export",
                "dataset": "scenarios",
                "status": "validation_failed",
                "format": request.format.value,
                "error": str(exc),
            },
        )
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(exc),
        ) from exc
    except Exception as exc:
        _record_export_audit(
            uow=uow,
            dataset="scenarios",
            status="failure",
            export_format=request.format,
            row_count=0,
            filename=None,
        )
        logger.exception(
            "export.failed",
            extra={
                "event": "export",
                "dataset": "scenarios",
                "status": "failure",
                "format": request.format.value,
            },
        )
        raise exc

    filename = f"scenarios-{_timestamp_suffix()}"

    if request.format == ExportFormat.CSV:
        stream = stream_scenarios_to_csv(scenarios)
        response = StreamingResponse(stream, media_type="text/csv")
        response.headers["Content-Disposition"] = f"attachment; filename={filename}.csv"
        _record_export_audit(
            uow=uow,
            dataset="scenarios",
            status="success",
            export_format=request.format,
            row_count=len(scenarios),
            filename=f"{filename}.csv",
        )
        logger.info(
            "export",
            extra={
                "event": "export",
                "dataset": "scenarios",
                "status": "success",
                "format": request.format.value,
                "row_count": len(scenarios),
                "filename": f"{filename}.csv",
            },
        )
        observe_export(
            dataset="scenarios",
            status="success",
            export_format=request.format.value,
            seconds=time.perf_counter() - start,
        )
        return response

    data = export_scenarios_to_excel(scenarios)
    _record_export_audit(
        uow=uow,
        dataset="scenarios",
        status="success",
        export_format=request.format,
        row_count=len(scenarios),
        filename=f"{filename}.xlsx",
    )
    logger.info(
        "export",
        extra={
            "event": "export",
            "dataset": "scenarios",
            "status": "success",
            "format": request.format.value,
            "row_count": len(scenarios),
            "filename": f"{filename}.xlsx",
        },
    )
    observe_export(
        dataset="scenarios",
        status="success",
        export_format=request.format.value,
        seconds=time.perf_counter() - start,
    )
    return StreamingResponse(
        iter([data]),
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        headers={
            "Content-Disposition": f"attachment; filename={filename}.xlsx",
        },
    )
