"""Reporting Engine — write-side services.

Service functions for creating, updating, and deleting reports, schedules,
KPIs, dashboards; executing reports; managing scheduled report lifecycle.
"""

from __future__ import annotations

from typing import Any

import structlog
from django.utils import timezone

from simorgh.apps.platform_core.export_registry import export_registry
from simorgh.apps.reporting.models import (
    DashboardDefinition,
    KPIDefinition,
    ReportDefinition,
    ReportExecution,
    ReportExecutionStatus,
    ReportFormat,
    ScheduledReport,
)

_log = structlog.get_logger("simorgh.reporting.services")


# ---------------------------------------------------------------------------
# Report definitions
# ---------------------------------------------------------------------------

def create_report(
    *,
    tenant,
    organization_node,
    code: str,
    name: str,
    **fields: Any,
) -> ReportDefinition:
    """Create a tenant-scoped report definition."""
    return ReportDefinition.objects.create(
        tenant=tenant,
        organization_node=organization_node,
        code=code,
        name=name,
        **fields,
    )


def update_report(report: ReportDefinition, **fields: Any) -> ReportDefinition:
    """Update fields on a report definition."""
    for key, value in fields.items():
        if hasattr(report, key):
            setattr(report, key, value)
    report.save()
    return report


def delete_report(report: ReportDefinition) -> None:
    """Soft-delete a report definition."""
    report.delete()


def sync_builtin_reports(
    *,
    tenant,
    organization_node,
    module: str,
) -> list[ReportDefinition]:
    """Synchronise registry-registered reports into DB rows for *module*.

    - Creates DB rows for registry specs not yet present.
    - Deactivates DB rows whose registry spec has been removed.
    - Returns the resulting active ReportDefinitions for the module.
    """
    from simorgh.apps.reporting.registry import report_registry

    registry_specs = {s.code: s for s in report_registry.list_for_module(module)}

    # Upsert registry specs as built-in DB rows.
    for code, spec in registry_specs.items():
        obj, _created = ReportDefinition.objects.update_or_create(
            tenant=tenant,
            code=code,
            defaults={
                "organization_node": organization_node,
                "name": spec.name,
                "description": spec.description,
                "module": spec.module,
                "entity_type": spec.entity_type,
                "resource": spec.resource,
                "columns": spec.columns,
                "filters_schema": spec.filters_schema,
                "parameters": spec.parameters,
                "group_by": spec.group_by,
                "sort_by": spec.sort_by,
                "page_size": spec.page_size,
                "export_formats": spec.export_formats,
                "is_active": spec.is_active,
                "is_builtin": True,
            },
        )
        if _created:
            _log.info("reporting.report.synced", code=code, module=module)

    # Deactivate DB rows whose registry spec was removed.
    existing = ReportDefinition.objects.filter(
        tenant=tenant, module=module, is_builtin=True,
    )
    for obj in existing:
        if obj.code not in registry_specs:
            obj.is_active = False
            obj.save(update_fields=["is_active"])

    return list(
        ReportDefinition.objects.filter(
            tenant=tenant, module=module, is_active=True,
        ).order_by("name")
    )


# ---------------------------------------------------------------------------
# Report execution
# ---------------------------------------------------------------------------

def execute_report(
    *,
    report: ReportDefinition,
    parameters: dict[str, Any] | None = None,
    filters: dict[str, Any] | None = None,
    format: str = "",
    triggered_by=None,
    tenant,
    organization_node,
) -> ReportExecution:
    """Create a ``ReportExecution`` and return it (status: PENDING).

    The actual data fetch and file generation should be dispatched
    asynchronously via Celery (see ``execute_report_async`` or
    the ``process_report_execution`` task).
    """
    execution = ReportExecution.objects.create(
        tenant=tenant,
        organization_node=organization_node,
        report=report,
        status=ReportExecutionStatus.PENDING,
        format=format or ReportFormat.CSV,
        parameters=parameters or {},
        filters_applied=filters or {},
        triggered_by=triggered_by,
    )
    _log.info(
        "reporting.execution.created",
        execution_id=execution.pk,
        report_code=report.code,
    )
    return execution


def execute_report_async(
    *,
    report: ReportDefinition,
    parameters: dict[str, Any] | None = None,
    filters: dict[str, Any] | None = None,
    format: str = "",
    triggered_by=None,
    tenant,
    organization_node,
) -> ReportExecution:
    """Create a ReportExecution and dispatch to Celery."""
    from simorgh.apps.reporting.tasks import process_report_execution

    execution = execute_report(
        report=report,
        parameters=parameters,
        filters=filters,
        format=format,
        triggered_by=triggered_by,
        tenant=tenant,
        organization_node=organization_node,
    )
    try:
        process_report_execution.delay(execution.pk)
    except Exception as exc:
        _log.warning(
            "reporting.execution.dispatch_failed",
            execution_id=execution.pk,
            error=str(exc),
        )
        execution.mark_failed(error_message=f"Dispatch failed: {exc}")
    return execution


def export_report_via_export_job(
    *,
    report: ReportDefinition,
    format: str = "csv",
    filters: dict[str, Any] | None = None,
    actor=None,
    tenant,
) -> ReportExecution:
    """Export a report by delegating to the existing ExportJob infrastructure.

    Uses ``platform_core.export_registry`` when the report's ``entity_type``
    has an ``ExportSpec`` registered.  For reports without an entity_type
    mapping, this falls back to a direct execution.
    """
    if report.entity_type and report.entity_type in export_registry:
        from simorgh.apps.platform_core.services import create_export_job

        create_export_job(
            tenant=tenant,
            entity_type=report.entity_type,
            filters=filters or {},
            format=format.upper(),
            actor=actor,
        )
        # Track via a lightweight execution record.
        return execute_report(
            report=report,
            filters=filters,
            format=format,
            triggered_by=actor,
            tenant=tenant,
            organization_node=report.organization_node,
        )

    # Fallback: direct async execution.
    return execute_report_async(
        report=report,
        filters=filters,
        format=format,
        triggered_by=actor,
        tenant=tenant,
        organization_node=report.organization_node,
    )


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

def create_schedule(
    *,
    tenant,
    organization_node,
    report: ReportDefinition,
    cron_expression: str,
    format: str = "csv",
    recipients: list[Any] | None = None,
) -> ScheduledReport:
    """Create a scheduled report delivery."""
    from simorgh.apps.reporting.tasks import compute_next_run

    now = timezone.now()
    next_run = compute_next_run(cron_expression, base=now)

    return ScheduledReport.objects.create(
        tenant=tenant,
        organization_node=organization_node,
        report=report,
        cron_expression=cron_expression,
        format=format,
        recipients=recipients or [],
        next_run_at=next_run,
    )


def update_schedule(
    schedule: ScheduledReport,
    **fields: Any,
) -> ScheduledReport:
    """Update fields on a scheduled report."""
    for key, value in fields.items():
        if hasattr(schedule, key):
            setattr(schedule, key, value)
    schedule.save()
    return schedule


def delete_schedule(schedule: ScheduledReport) -> None:
    """Soft-delete a scheduled report."""
    schedule.delete()


def toggle_schedule(schedule: ScheduledReport) -> ScheduledReport:
    """Flip ``is_active`` on a schedule."""
    schedule.is_active = not schedule.is_active
    schedule.save(update_fields=["is_active"])
    return schedule


# ---------------------------------------------------------------------------
# KPI definitions
# ---------------------------------------------------------------------------

def create_kpi(
    *,
    tenant,
    organization_node,
    code: str,
    name: str,
    resource: str,
    **fields: Any,
) -> KPIDefinition:
    """Create a KPI definition."""
    return KPIDefinition.objects.create(
        tenant=tenant,
        organization_node=organization_node,
        code=code,
        name=name,
        resource=resource,
        **fields,
    )


def update_kpi(kpi: KPIDefinition, **fields: Any) -> KPIDefinition:
    """Update a KPI definition."""
    for key, value in fields.items():
        if hasattr(kpi, key):
            setattr(kpi, key, value)
    kpi.save()
    return kpi


def delete_kpi(kpi: KPIDefinition) -> None:
    """Soft-delete a KPI definition."""
    kpi.delete()


def sync_builtin_kpis(
    *,
    tenant,
    organization_node,
    module: str,
) -> list[KPIDefinition]:
    """Synchronise registry-registered KPIs into DB rows for *module*."""
    from simorgh.apps.reporting.registry import kpi_registry

    registry_specs = {s.code: s for s in kpi_registry.list_for_module(module)}

    for code, spec in registry_specs.items():
        KPIDefinition.objects.update_or_create(
            tenant=tenant,
            code=code,
            defaults={
                "organization_node": organization_node,
                "name": spec.name,
                "description": spec.description,
                "module": spec.module,
                "resource": spec.resource,
                "format": spec.format,
                "unit": spec.unit,
                "decimal_places": spec.decimal_places,
                "trend_resource": spec.trend_resource,
                "is_active": spec.is_active,
                "is_builtin": True,
                "threshold_warning": spec.threshold_warning,
                "threshold_critical": spec.threshold_critical,
            },
        )

    existing = KPIDefinition.objects.filter(
        tenant=tenant, module=module, is_builtin=True,
    )
    for obj in existing:
        if obj.code not in registry_specs:
            obj.is_active = False
            obj.save(update_fields=["is_active"])

    return list(
        KPIDefinition.objects.filter(
            tenant=tenant, module=module, is_active=True,
        ).order_by("name")
    )


# ---------------------------------------------------------------------------
# Dashboard definitions
# ---------------------------------------------------------------------------

def create_dashboard(
    *,
    tenant,
    organization_node,
    code: str,
    name: str,
    **fields: Any,
) -> DashboardDefinition:
    """Create a dashboard definition."""
    return DashboardDefinition.objects.create(
        tenant=tenant,
        organization_node=organization_node,
        code=code,
        name=name,
        **fields,
    )


def update_dashboard(dashboard: DashboardDefinition, **fields: Any) -> DashboardDefinition:
    """Update a dashboard definition."""
    for key, value in fields.items():
        if hasattr(dashboard, key):
            setattr(dashboard, key, value)
    dashboard.save()
    return dashboard


def delete_dashboard(dashboard: DashboardDefinition) -> None:
    """Soft-delete a dashboard definition."""
    dashboard.delete()


def sync_builtin_dashboards(
    *,
    tenant,
    organization_node,
    module: str,
) -> list[DashboardDefinition]:
    """Synchronise registry-registered dashboards into DB rows for *module*."""
    from simorgh.apps.reporting.registry import dashboard_registry

    registry_specs = {s.code: s for s in dashboard_registry.list_for_module(module)}

    for code, spec in registry_specs.items():
        DashboardDefinition.objects.update_or_create(
            tenant=tenant,
            code=code,
            defaults={
                "organization_node": organization_node,
                "name": spec.name,
                "description": spec.description,
                "module": spec.module,
                "layout": spec.layout,
                "is_active": spec.is_active,
                "is_default": spec.is_default,
                "is_builtin": True,
            },
        )

    existing = DashboardDefinition.objects.filter(
        tenant=tenant, module=module, is_builtin=True,
    )
    for obj in existing:
        if obj.code not in registry_specs:
            obj.is_active = False
            obj.save(update_fields=["is_active"])

    return list(
        DashboardDefinition.objects.filter(
            tenant=tenant, module=module, is_active=True,
        ).order_by("name")
    )
