"""Reporting Engine — API views.

All endpoints are permission-aware, tenant-scoped, and auditable.
"""

from __future__ import annotations

from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from rest_framework import status as http_status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.reporting import selectors, services
from simorgh.apps.reporting.models import (
    DashboardDefinition,
    KPIDefinition,
    ReportDefinition,
    ScheduledReport,
)
from simorgh.apps.reporting.serializers import (
    DashboardDefinitionSerializer,
    KPIDefinitionSerializer,
    ReportDefinitionSerializer,
    ReportExportRequestSerializer,
    ReportRunRequestSerializer,
    ScheduledReportSerializer,
)

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _tenant(request: Request):
    """Return the tenant for the request or raise PermissionDenied."""
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        raise PermissionDenied("No active tenant.")
    return tenant


def _require_perm(request: Request, codename: str) -> None:
    """Raise PermissionDenied when the user lacks *codename*."""
    if not request.user.has_perm(codename):
        raise PermissionDenied(codename)


def _org_node(tenant):
    """Return the first org node for this tenant."""
    from simorgh.apps.organizations.models import OrganizationNode

    org_node = OrganizationNode.objects.filter(tenant=tenant).first()
    if org_node is None:
        from django.core.exceptions import ValidationError
        raise ValidationError("No organization node found for this tenant.")
    return org_node


def _report_payload(report: ReportDefinition) -> dict:
    """Serialize a ReportDefinition to a plain dict."""
    return ReportDefinitionSerializer(report).data


def _schedule_payload(schedule: ScheduledReport) -> dict:
    """Serialize a ScheduledReport to a plain dict."""
    return ScheduledReportSerializer(schedule).data


def _kpi_payload(kpi: KPIDefinition) -> dict:
    """Serialize a KPIDefinition to a plain dict."""
    return KPIDefinitionSerializer(kpi).data


def _dashboard_payload(dashboard: DashboardDefinition) -> dict:
    """Serialize a DashboardDefinition to a plain dict."""
    return DashboardDefinitionSerializer(dashboard).data


# ---------------------------------------------------------------------------
# Reports
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def report_list(request: Request) -> Response:
    """GET/POST /api/v1/reporting/reports/"""
    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, "reporting.reports.view")
        module = request.query_params.get("module", "")
        reports = selectors.list_reports(tenant_id=tenant.pk, module=module)
        return Response(
            {"results": [_report_payload(r) for r in reports], "count": reports.count()}
        )

    # POST
    _require_perm(request, "reporting.reports.create")
    serializer = ReportDefinitionSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)

    org_node = _org_node(tenant)
    report = services.create_report(
        tenant=tenant,
        organization_node=org_node,
        **serializer.validated_data,
    )
    return Response(_report_payload(report), status=http_status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def report_detail(request: Request, report_id: int) -> Response:
    """GET/PATCH/DELETE /api/v1/reporting/reports/{id}/"""
    tenant = _tenant(request)
    report = get_object_or_404(ReportDefinition, pk=report_id, tenant=tenant)

    if request.method == "GET":
        _require_perm(request, "reporting.reports.view")
        return Response(_report_payload(report))

    if request.method == "PATCH":
        _require_perm(request, "reporting.reports.update")
        serializer = ReportDefinitionSerializer(report, data=request.data, partial=True)
        if not serializer.is_valid():
            return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)
        report = services.update_report(report, **serializer.validated_data)
        return Response(_report_payload(report))

    # DELETE
    _require_perm(request, "reporting.reports.delete")
    services.delete_report(report)
    return Response(status=http_status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def report_run(request: Request, report_id: int) -> Response:
    """POST /api/v1/reporting/reports/{id}/run/ — execute a report."""
    tenant = _tenant(request)
    _require_perm(request, "reporting.reports.execute")

    report = get_object_or_404(ReportDefinition, pk=report_id, tenant=tenant)
    serializer = ReportRunRequestSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)

    data = serializer.validated_data
    execution = services.execute_report_async(
        report=report,
        parameters=data.get("parameters"),
        filters=data.get("filters"),
        format=data.get("format", "csv"),
        triggered_by=request.user,
        tenant=tenant,
        organization_node=report.organization_node,
    )
    return Response(
        {
            "execution_id": execution.pk,
            "public_id": str(execution.public_id),
            "status": execution.status,
        },
        status=http_status.HTTP_202_ACCEPTED,
    )


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def report_export(request: Request, report_id: int) -> Response:
    """POST /api/v1/reporting/reports/{id}/export/ — export a report."""
    tenant = _tenant(request)
    _require_perm(request, "reporting.reports.export")

    report = get_object_or_404(ReportDefinition, pk=report_id, tenant=tenant)
    serializer = ReportExportRequestSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)

    data = serializer.validated_data
    execution = services.export_report_via_export_job(
        report=report,
        format=data.get("format", "csv"),
        filters=data.get("filters"),
        actor=request.user,
        tenant=tenant,
    )
    return Response(
        {
            "execution_id": execution.pk,
            "public_id": str(execution.public_id),
            "status": execution.status,
        },
        status=http_status.HTTP_202_ACCEPTED,
    )


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def report_executions(request: Request, report_id: int) -> Response:
    """GET /api/v1/reporting/reports/{id}/executions/"""
    tenant = _tenant(request)
    _require_perm(request, "reporting.executions.view")

    report = get_object_or_404(ReportDefinition, pk=report_id, tenant=tenant)
    status_param = request.query_params.get("status", "")
    executions = selectors.list_executions(
        tenant_id=tenant.pk,
        report_id=report.pk,
        status=status_param,
        limit=100,
    )
    from simorgh.apps.reporting.serializers import ReportExecutionSerializer

    return Response(
        {
            "results": [ReportExecutionSerializer(ex).data for ex in executions],
            "count": len(executions),
        }
    )


# ---------------------------------------------------------------------------
# Scheduled reports
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def schedule_list(request: Request, report_id: int) -> Response:
    """GET/POST /api/v1/reporting/reports/{id}/schedules/"""
    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, "reporting.schedules.view")
        schedules = selectors.list_schedules(tenant_id=tenant.pk, report_id=report_id)
        return Response(
            {"results": [_schedule_payload(s) for s in schedules], "count": schedules.count()}
        )

    # POST
    _require_perm(request, "reporting.schedules.create")
    report = get_object_or_404(ReportDefinition, pk=report_id, tenant=tenant)

    data = request.data
    if not data.get("cron_expression"):
        return Response(
            {"error": "cron_expression is required"},
            status=http_status.HTTP_400_BAD_REQUEST,
        )

    org_node = _org_node(tenant)
    schedule = services.create_schedule(
        tenant=tenant,
        organization_node=org_node,
        report=report,
        cron_expression=data["cron_expression"],
        format=data.get("format", "csv"),
        recipients=data.get("recipients", []),
    )
    return Response(_schedule_payload(schedule), status=http_status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def schedule_detail(request: Request, schedule_id: int) -> Response:
    """GET/PATCH/DELETE /api/v1/reporting/schedules/{id}/"""
    tenant = _tenant(request)
    schedule = get_object_or_404(ScheduledReport, pk=schedule_id, tenant=tenant)

    if request.method == "GET":
        _require_perm(request, "reporting.schedules.view")
        return Response(_schedule_payload(schedule))

    if request.method == "PATCH":
        _require_perm(request, "reporting.schedules.update")
        allowed = ("cron_expression", "format", "recipients", "is_active")
        updates = {k: v for k, v in request.data.items() if k in allowed and v is not None}
        schedule = services.update_schedule(schedule, **updates)
        return Response(_schedule_payload(schedule))

    # DELETE
    _require_perm(request, "reporting.schedules.delete")
    services.delete_schedule(schedule)
    return Response(status=http_status.HTTP_204_NO_CONTENT)


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def schedule_toggle(request: Request, schedule_id: int) -> Response:
    """POST /api/v1/reporting/schedules/{id}/toggle/"""
    tenant = _tenant(request)
    _require_perm(request, "reporting.schedules.update")
    schedule = get_object_or_404(ScheduledReport, pk=schedule_id, tenant=tenant)
    schedule = services.toggle_schedule(schedule)
    return Response(_schedule_payload(schedule))


# ---------------------------------------------------------------------------
# KPIs
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def kpi_list(request: Request) -> Response:
    """GET/POST /api/v1/reporting/kpis/"""
    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, "reporting.kpis.view")
        module = request.query_params.get("module", "")
        kpis = selectors.list_kpis(tenant_id=tenant.pk, module=module)
        return Response(
            {"results": [_kpi_payload(k) for k in kpis], "count": kpis.count()}
        )

    # POST
    _require_perm(request, "reporting.kpis.create")
    serializer = KPIDefinitionSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)

    org_node = _org_node(tenant)
    kpi = services.create_kpi(
        tenant=tenant,
        organization_node=org_node,
        **serializer.validated_data,
    )
    return Response(_kpi_payload(kpi), status=http_status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def kpi_detail(request: Request, kpi_id: int) -> Response:
    """GET/PATCH/DELETE /api/v1/reporting/kpis/{id}/"""
    tenant = _tenant(request)
    kpi = get_object_or_404(KPIDefinition, pk=kpi_id, tenant=tenant)

    if request.method == "GET":
        _require_perm(request, "reporting.kpis.view")
        return Response(_kpi_payload(kpi))

    if request.method == "PATCH":
        _require_perm(request, "reporting.kpis.update")
        serializer = KPIDefinitionSerializer(kpi, data=request.data, partial=True)
        if not serializer.is_valid():
            return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)
        kpi = services.update_kpi(kpi, **serializer.validated_data)
        return Response(_kpi_payload(kpi))

    # DELETE
    _require_perm(request, "reporting.kpis.delete")
    services.delete_kpi(kpi)
    return Response(status=http_status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Dashboards
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def dashboard_list(request: Request) -> Response:
    """GET/POST /api/v1/reporting/dashboards/"""
    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, "reporting.dashboards.view")
        module = request.query_params.get("module", "")
        dashboards = selectors.list_dashboards(tenant_id=tenant.pk, module=module)
        return Response(
            {"results": [_dashboard_payload(d) for d in dashboards], "count": dashboards.count()}
        )

    # POST
    _require_perm(request, "reporting.dashboards.create")
    serializer = DashboardDefinitionSerializer(data=request.data)
    if not serializer.is_valid():
        return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)

    org_node = _org_node(tenant)
    dashboard = services.create_dashboard(
        tenant=tenant,
        organization_node=org_node,
        **serializer.validated_data,
    )
    return Response(_dashboard_payload(dashboard), status=http_status.HTTP_201_CREATED)


@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def dashboard_detail(request: Request, dashboard_id: int) -> Response:
    """GET/PATCH/DELETE /api/v1/reporting/dashboards/{id}/"""
    tenant = _tenant(request)
    dashboard = get_object_or_404(DashboardDefinition, pk=dashboard_id, tenant=tenant)

    if request.method == "GET":
        _require_perm(request, "reporting.dashboards.view")
        return Response(_dashboard_payload(dashboard))

    if request.method == "PATCH":
        _require_perm(request, "reporting.dashboards.update")
        serializer = DashboardDefinitionSerializer(dashboard, data=request.data, partial=True)
        if not serializer.is_valid():
            return Response(serializer.errors, status=http_status.HTTP_400_BAD_REQUEST)
        dashboard = services.update_dashboard(dashboard, **serializer.validated_data)
        return Response(_dashboard_payload(dashboard))

    # DELETE
    _require_perm(request, "reporting.dashboards.delete")
    services.delete_dashboard(dashboard)
    return Response(status=http_status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Registry discovery
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def registry_report_list(request: Request) -> Response:
    """GET /api/v1/reporting/registry/reports/ — list registered report specs."""
    _tenant(request)
    _require_perm(request, "reporting.reports.view")

    from simorgh.apps.reporting.registry import report_registry

    specs = report_registry.list_all()
    module_filter = request.query_params.get("module", "")
    if module_filter:
        specs = report_registry.list_for_module(module_filter)

    results = [
        {
            "code": s.code,
            "name": s.name,
            "module": s.module,
            "resource": s.resource,
            "entity_type": s.entity_type,
            "columns": s.columns,
            "filters_schema": s.filters_schema,
            "export_formats": s.export_formats,
            "permission": s.permission,
        }
        for s in specs
    ]
    return Response({"results": results, "count": len(results)})


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def registry_kpi_list(request: Request) -> Response:
    """GET /api/v1/reporting/registry/kpis/ — list registered KPI specs."""
    _tenant(request)
    _require_perm(request, "reporting.kpis.view")

    from simorgh.apps.reporting.registry import kpi_registry

    specs = kpi_registry.list_all()
    module_filter = request.query_params.get("module", "")
    if module_filter:
        specs = kpi_registry.list_for_module(module_filter)

    results = [
        {
            "code": s.code,
            "name": s.name,
            "module": s.module,
            "resource": s.resource,
            "format": s.format,
            "unit": s.unit,
            "trend_resource": s.trend_resource,
            "permission": s.permission,
        }
        for s in specs
    ]
    return Response({"results": results, "count": len(results)})


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def registry_dashboard_list(request: Request) -> Response:
    """GET /api/v1/reporting/registry/dashboards/ — list registered dashboard specs."""
    _tenant(request)
    _require_perm(request, "reporting.dashboards.view")

    from simorgh.apps.reporting.registry import dashboard_registry

    specs = dashboard_registry.list_all()
    module_filter = request.query_params.get("module", "")
    if module_filter:
        specs = dashboard_registry.list_for_module(module_filter)

    results = [
        {
            "code": s.code,
            "name": s.name,
            "module": s.module,
            "layout": s.layout,
            "permission": s.permission,
            "is_default": s.is_default,
        }
        for s in specs
    ]
    return Response({"results": results, "count": len(results)})
