"""Reporting Engine — registries for module-level report/KPI/dashboard specs.

Usage (in a module's ``apps.py`` ``ready()`` hook)::

    from simorgh.apps.reporting.registry import (
        report_registry,
        kpi_registry,
        dashboard_registry,
        ReportSpec,
        KPISpec,
        DashboardSpec,
    )

    report_registry.register(ReportSpec(
        code="crm.leads_by_status",
        name="Leads by Status",
        module="crm",
        resource="/api/v1/crm/leads/report/",
        columns=[...],
        filters_schema=[...],
    ))
"""

from __future__ import annotations

from dataclasses import dataclass, field
from threading import Lock
from typing import Any

__all__ = [
    "DashboardRegistry",
    "DashboardSpec",
    "DashboardWidgetSpec",
    "KPIRegistry",
    "KPISpec",
    "ReportRegistry",
    "ReportSpec",
    "dashboard_registry",
    "kpi_registry",
    "report_registry",
]


# ---------------------------------------------------------------------------
# Spec dataclasses
# ---------------------------------------------------------------------------

@dataclass
class ReportSpec:
    """Schema + metadata for a module-registered report."""

    code: str
    name: str
    module: str
    resource: str
    columns: list[dict[str, Any]] = field(default_factory=list)
    filters_schema: list[dict[str, Any]] = field(default_factory=list)
    parameters: dict[str, Any] = field(default_factory=dict)
    group_by: list[str] = field(default_factory=list)
    sort_by: str = ""
    page_size: int = 100
    export_formats: list[str] = field(default_factory=list)
    description: str = ""
    entity_type: str = ""
    permission: str = ""
    is_active: bool = True


@dataclass
class KPISpec:
    """Schema for a module-registered KPI metric."""

    code: str
    name: str
    module: str
    resource: str
    format: str = "number"  # number / currency / percent / duration
    unit: str = ""
    decimal_places: int = 0
    trend_resource: str = ""
    description: str = ""
    permission: str = ""
    is_active: bool = True
    threshold_warning: float | None = None
    threshold_critical: float | None = None


@dataclass
class DashboardWidgetSpec:
    """A single widget on a dashboard."""

    widget: str  # kpi / chart / data-table / calendar-mini / activity-stream / custom
    col_span: int = 1
    kpi_code: str = ""
    resource: str = ""
    chart_type: str = ""  # line / bar / area / donut
    title_key: str = ""
    config: dict[str, Any] = field(default_factory=dict)


@dataclass
class DashboardSpec:
    """Schema for a module-registered dashboard."""

    code: str
    name: str
    module: str
    layout: dict[str, Any] = field(default_factory=dict)
    description: str = ""
    permission: str = ""
    is_default: bool = False
    is_active: bool = True


# ---------------------------------------------------------------------------
# Registries
# ---------------------------------------------------------------------------

class ReportRegistry:
    """Singleton registry mapping ``code`` → ``ReportSpec``."""

    def __init__(self) -> None:
        self._specs: dict[str, ReportSpec] = {}
        self._lock = Lock()

    def register(self, spec: ReportSpec) -> None:
        with self._lock:
            if spec.code in self._specs:
                raise ValueError(
                    f"ReportSpec with code {spec.code!r} is already registered."
                )
            self._specs[spec.code] = spec

    def replace(self, spec: ReportSpec) -> None:
        with self._lock:
            self._specs[spec.code] = spec

    def unregister(self, code: str) -> None:
        with self._lock:
            self._specs.pop(code, None)

    def get(self, code: str) -> ReportSpec | None:
        return self._specs.get(code)

    def list_for_module(self, module: str) -> list[ReportSpec]:
        return [s for s in self._specs.values() if s.module == module]

    def list_all(self) -> list[ReportSpec]:
        return list(self._specs.values())

    def __len__(self) -> int:
        return len(self._specs)

    def __contains__(self, code: str) -> bool:
        return code in self._specs


class KPIRegistry:
    """Singleton registry mapping ``code`` → ``KPISpec``."""

    def __init__(self) -> None:
        self._specs: dict[str, KPISpec] = {}
        self._lock = Lock()

    def register(self, spec: KPISpec) -> None:
        with self._lock:
            if spec.code in self._specs:
                raise ValueError(
                    f"KPISpec with code {spec.code!r} is already registered."
                )
            self._specs[spec.code] = spec

    def replace(self, spec: KPISpec) -> None:
        with self._lock:
            self._specs[spec.code] = spec

    def unregister(self, code: str) -> None:
        with self._lock:
            self._specs.pop(code, None)

    def get(self, code: str) -> KPISpec | None:
        return self._specs.get(code)

    def list_for_module(self, module: str) -> list[KPISpec]:
        return [s for s in self._specs.values() if s.module == module]

    def list_all(self) -> list[KPISpec]:
        return list(self._specs.values())

    def __len__(self) -> int:
        return len(self._specs)

    def __contains__(self, code: str) -> bool:
        return code in self._specs


class DashboardRegistry:
    """Singleton registry mapping ``code`` → ``DashboardSpec``."""

    def __init__(self) -> None:
        self._specs: dict[str, DashboardSpec] = {}
        self._lock = Lock()

    def register(self, spec: DashboardSpec) -> None:
        with self._lock:
            if spec.code in self._specs:
                raise ValueError(
                    f"DashboardSpec with code {spec.code!r} is already registered."
                )
            self._specs[spec.code] = spec

    def replace(self, spec: DashboardSpec) -> None:
        with self._lock:
            self._specs[spec.code] = spec

    def unregister(self, code: str) -> None:
        with self._lock:
            self._specs.pop(code, None)

    def get(self, code: str) -> DashboardSpec | None:
        return self._specs.get(code)

    def list_for_module(self, module: str) -> list[DashboardSpec]:
        return [s for s in self._specs.values() if s.module == module]

    def list_all(self) -> list[DashboardSpec]:
        return list(self._specs.values())

    def __len__(self) -> int:
        return len(self._specs)

    def __contains__(self, code: str) -> bool:
        return code in self._specs


def reset_registry_for_tests() -> None:
    """Reset all registries — for test isolation only."""
    report_registry._specs.clear()
    kpi_registry._specs.clear()
    dashboard_registry._specs.clear()


# Module-level singletons
report_registry = ReportRegistry()
kpi_registry = KPIRegistry()
dashboard_registry = DashboardRegistry()
