"""Views & Visualization — business services.

All write operations for view, dashboard, chart configurations.
Read-only queries live in selectors.py.
"""

from __future__ import annotations

from typing import Any

from django.db import transaction

from simorgh.apps.events.bus import dispatch
from simorgh.apps.visualization.models import (
    ChartConfiguration,
    DashboardConfiguration,
    DashboardWidget,
    ViewConfiguration,
    ViewFilter,
    ViewGrouping,
)
from simorgh.core.audit import record_service_event

# ── View Configuration ──────────────────────────────────────────────────────

def create_view_configuration(
    *,
    tenant_id: int,
    organization_node_id: int,
    created_by_id: int,
    entity_name: str,
    view_type: str,
    name: str,
    config: dict[str, Any] | None = None,
    is_default: bool = False,
    visibility: str = "private",
) -> ViewConfiguration:
    with transaction.atomic():
        view = ViewConfiguration.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            created_by_id=created_by_id,
            updated_by_id=created_by_id,
            entity_name=entity_name,
            view_type=view_type,
            name=name,
            config=config or {},
            is_default=is_default,
            visibility=visibility,
        )
    record_service_event(
        "visualization.view_created",
        resource=view,
        after={"entity_name": entity_name, "view_type": view_type, "name": name},
    )
    dispatch(
        "visualization.view_created",
        {
            "tenant_id": tenant_id,
            "view_id": str(view.pk),
            "entity_name": entity_name,
            "view_type": view_type,
        },
    )
    return view


def update_view_configuration(
    *,
    view_id: int,
    updated_by_id: int,
    name: str | None = None,
    config: dict[str, Any] | None = None,
    is_default: bool | None = None,
    visibility: str | None = None,
) -> ViewConfiguration:
    view = ViewConfiguration.objects.get(pk=view_id)
    before = {"name": view.name, "config": view.config, "is_default": view.is_default, "visibility": view.visibility}
    if name is not None:
        view.name = name
    if config is not None:
        view.config = config
    if is_default is not None:
        view.is_default = is_default
    if visibility is not None:
        view.visibility = visibility
    view.updated_by_id = updated_by_id
    view.save(update_fields=["name", "config", "is_default", "visibility", "updated_by_id"])
    record_service_event(
        "visualization.view_updated",
        resource=view,
        before=before,
        after={"name": view.name, "config": view.config, "is_default": view.is_default, "visibility": view.visibility},
    )
    dispatch(
        "visualization.view_updated",
        {
            "tenant_id": view.tenant_id,
            "view_id": str(view.pk),
            "entity_name": view.entity_name,
            "view_type": view.view_type,
        },
    )
    return view


def delete_view_configuration(*, view_id: int) -> None:
    view = ViewConfiguration.objects.get(pk=view_id)
    tenant_id = view.tenant_id
    record_service_event(
        "visualization.view_deleted",
        resource=view,
        before={"name": view.name, "entity_name": view.entity_name, "view_type": view.view_type},
    )
    dispatch(
        "visualization.view_deleted",
        {
            "tenant_id": tenant_id,
            "view_id": str(view.pk),
            "entity_name": view.entity_name,
            "view_type": view.view_type,
        },
    )
    view.delete()


def set_default_view(*, view_id: int, entity_name: str) -> ViewConfiguration:
    """Set a view as the default for its entity (unsetting any previous default)."""
    view = ViewConfiguration.objects.get(pk=view_id)
    ViewConfiguration.objects.filter(
        tenant_id=view.tenant_id,
        entity_name=entity_name,
        is_default=True,
    ).update(is_default=False)
    view.is_default = True
    view.save(update_fields=["is_default"])
    record_service_event(
        "visualization.view_set_default",
        resource=view,
        after={"entity_name": entity_name},
    )
    return view


# ── View Filters ────────────────────────────────────────────────────────────

def create_view_filter(
    *,
    tenant_id: int,
    organization_node_id: int,
    created_by_id: int,
    view_id: int,
    name: str,
    filter_config: dict[str, Any] | None = None,
) -> ViewFilter:
    f = ViewFilter.objects.create(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        created_by_id=created_by_id,
        updated_by_id=created_by_id,
        view_id=view_id,
        name=name,
        filter_config=filter_config or {},
    )
    record_service_event("visualization.view_filter_created", resource=f, after={"name": name})
    return f


def delete_view_filter(*, filter_id: int) -> None:
    ViewFilter.objects.filter(pk=filter_id).delete()


# ── View Groupings ──────────────────────────────────────────────────────────

def create_view_grouping(
    *,
    tenant_id: int,
    organization_node_id: int,
    view_id: int,
    field_name: str,
    label_key: str = "",
    color: str = "",
    sort_order: int = 0,
) -> ViewGrouping:
    return ViewGrouping.objects.create(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        view_id=view_id,
        field_name=field_name,
        label_key=label_key,
        color=color,
        sort_order=sort_order,
    )


def update_view_groupings(
    *,
    view_id: int,
    groupings: list[dict[str, Any]],
) -> list[ViewGrouping]:
    """Replace all groupings for a view."""
    with transaction.atomic():
        ViewGrouping.objects.filter(view_id=view_id).delete()
        result: list[ViewGrouping] = []
        view = ViewConfiguration.objects.get(pk=view_id)
        for i, g in enumerate(groupings):
            result.append(
                ViewGrouping.objects.create(
                    tenant_id=view.tenant_id,
                    organization_node_id=view.organization_node_id,
                    view_id=view_id,
                    field_name=g["field_name"],
                    label_key=g.get("label_key", ""),
                    color=g.get("color", ""),
                    sort_order=g.get("sort_order", i),
                )
            )
        return result


# ── Dashboard Configuration ─────────────────────────────────────────────────

def create_dashboard(
    *,
    tenant_id: int,
    organization_node_id: int,
    created_by_id: int,
    name: str,
    label_key: str = "",
    icon: str = "grid",
    visibility: str = "private",
    config: dict[str, Any] | None = None,
) -> DashboardConfiguration:
    with transaction.atomic():
        dashboard = DashboardConfiguration.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            created_by_id=created_by_id,
            updated_by_id=created_by_id,
            name=name,
            label_key=label_key,
            icon=icon,
            visibility=visibility,
            config=config or {},
        )
    record_service_event(
        "visualization.dashboard_created",
        resource=dashboard,
        after={"name": name, "visibility": visibility},
    )
    dispatch(
        "visualization.dashboard_created",
        {
            "tenant_id": tenant_id,
            "dashboard_id": str(dashboard.pk),
            "name": name,
        },
    )
    return dashboard


def update_dashboard(
    *,
    dashboard_id: int,
    updated_by_id: int,
    name: str | None = None,
    label_key: str | None = None,
    icon: str | None = None,
    visibility: str | None = None,
    config: dict[str, Any] | None = None,
) -> DashboardConfiguration:
    dashboard = DashboardConfiguration.objects.get(pk=dashboard_id)
    before = {"name": dashboard.name, "label_key": dashboard.label_key, "visibility": dashboard.visibility}
    if name is not None:
        dashboard.name = name
    if label_key is not None:
        dashboard.label_key = label_key
    if icon is not None:
        dashboard.icon = icon
    if visibility is not None:
        dashboard.visibility = visibility
    if config is not None:
        dashboard.config = config
    dashboard.updated_by_id = updated_by_id
    dashboard.save(update_fields=["name", "label_key", "icon", "visibility", "config", "updated_by_id"])
    record_service_event(
        "visualization.dashboard_updated",
        resource=dashboard,
        before=before,
        after={"name": dashboard.name, "label_key": dashboard.label_key, "visibility": dashboard.visibility},
    )
    dispatch(
        "visualization.dashboard_updated",
        {
            "tenant_id": dashboard.tenant_id,
            "dashboard_id": str(dashboard.pk),
            "name": dashboard.name,
        },
    )
    return dashboard


def delete_dashboard(*, dashboard_id: int) -> None:
    dashboard = DashboardConfiguration.objects.get(pk=dashboard_id)
    tenant_id = dashboard.tenant_id
    record_service_event(
        "visualization.dashboard_deleted",
        resource=dashboard,
        before={"name": dashboard.name},
    )
    dispatch(
        "visualization.dashboard_deleted",
        {
            "tenant_id": tenant_id,
            "dashboard_id": str(dashboard.pk),
            "name": dashboard.name,
        },
    )
    dashboard.delete()


# ── Dashboard Widgets ──────────────────────────────────────────────────────

def create_dashboard_widget(
    *,
    tenant_id: int,
    organization_node_id: int,
    dashboard_id: int,
    widget_type: str,
    title_key: str,
    row_index: int = 0,
    col_span: int = 3,
    sort_order: int = 0,
    config: dict[str, Any] | None = None,
) -> DashboardWidget:
    widget = DashboardWidget.objects.create(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        dashboard_id=dashboard_id,
        widget_type=widget_type,
        title_key=title_key,
        row_index=row_index,
        col_span=col_span,
        sort_order=sort_order,
        config=config or {},
    )
    record_service_event(
        "visualization.widget_added",
        resource=widget,
        after={"widget_type": widget_type, "dashboard_id": dashboard_id},
    )
    dispatch(
        "visualization.widget_added",
        {
            "tenant_id": tenant_id,
            "dashboard_id": dashboard_id,
            "widget_id": str(widget.pk),
            "widget_type": widget_type,
        },
    )
    return widget


def update_dashboard_widgets(
    *,
    dashboard_id: int,
    widgets_data: list[dict[str, Any]],
) -> list[DashboardWidget]:
    """Replace all widgets for a dashboard."""
    with transaction.atomic():
        DashboardWidget.objects.filter(dashboard_id=dashboard_id).delete()
        dashboard = DashboardConfiguration.objects.get(pk=dashboard_id)
        result: list[DashboardWidget] = []
        for i, w in enumerate(widgets_data):
            result.append(
                DashboardWidget.objects.create(
                    tenant_id=dashboard.tenant_id,
                    organization_node_id=dashboard.organization_node_id,
                    dashboard_id=dashboard_id,
                    widget_type=w["widget_type"],
                    title_key=w.get("title_key", ""),
                    row_index=w.get("row_index", 0),
                    col_span=w.get("col_span", 3),
                    sort_order=w.get("sort_order", i),
                    config=w.get("config", {}),
                )
            )
        dispatch(
            "visualization.widget_added",
            {
                "tenant_id": dashboard.tenant_id,
                "dashboard_id": dashboard_id,
                "widget_count": len(result),
            },
        )
        return result


# ── Chart Configuration ─────────────────────────────────────────────────────

def create_chart_configuration(
    *,
    tenant_id: int,
    organization_node_id: int,
    created_by_id: int,
    entity_name: str,
    chart_type: str,
    name: str,
    config: dict[str, Any] | None = None,
    visibility: str = "private",
) -> ChartConfiguration:
    with transaction.atomic():
        chart = ChartConfiguration.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            created_by_id=created_by_id,
            updated_by_id=created_by_id,
            entity_name=entity_name,
            chart_type=chart_type,
            name=name,
            config=config or {},
            visibility=visibility,
        )
    record_service_event(
        "visualization.chart_created",
        resource=chart,
        after={"entity_name": entity_name, "chart_type": chart_type, "name": name},
    )
    dispatch(
        "visualization.chart_created",
        {
            "tenant_id": tenant_id,
            "chart_id": str(chart.pk),
            "entity_name": entity_name,
            "chart_type": chart_type,
        },
    )
    return chart


def update_chart_configuration(
    *,
    chart_id: int,
    updated_by_id: int,
    name: str | None = None,
    config: dict[str, Any] | None = None,
    visibility: str | None = None,
) -> ChartConfiguration:
    chart = ChartConfiguration.objects.get(pk=chart_id)
    before = {"name": chart.name, "config": chart.config, "visibility": chart.visibility}
    if name is not None:
        chart.name = name
    if config is not None:
        chart.config = config
    if visibility is not None:
        chart.visibility = visibility
    chart.updated_by_id = updated_by_id
    chart.save(update_fields=["name", "config", "visibility", "updated_by_id"])
    record_service_event(
        "visualization.chart_updated",
        resource=chart,
        before=before,
        after={"name": chart.name, "config": chart.config, "visibility": chart.visibility},
    )
    dispatch(
        "visualization.chart_updated",
        {
            "tenant_id": chart.tenant_id,
            "chart_id": str(chart.pk),
            "entity_name": chart.entity_name,
            "chart_type": chart.chart_type,
        },
    )
    return chart


def delete_chart_configuration(*, chart_id: int) -> None:
    chart = ChartConfiguration.objects.get(pk=chart_id)
    tenant_id = chart.tenant_id
    record_service_event(
        "visualization.chart_deleted",
        resource=chart,
        before={"name": chart.name, "entity_name": chart.entity_name, "chart_type": chart.chart_type},
    )
    dispatch(
        "visualization.chart_deleted",
        {
            "tenant_id": tenant_id,
            "chart_id": str(chart.pk),
            "entity_name": chart.entity_name,
            "chart_type": chart.chart_type,
        },
    )
    chart.delete()
