"""Import Engine — registry for entity-type import specifications.

Usage (in a module's ``apps.py`` or ``ready()`` hook)::

    from simorgh.apps.platform_core.import_registry import import_registry, ImportSpec, ColumnSpec

    import_registry.register(ImportSpec(
        entity_type="crm.lead",
        columns=[
            ColumnSpec(key="full_name", label="Full Name", required=True, col_type="text"),
            ColumnSpec(key="email",     label="Email",     required=True, col_type="email"),
            ColumnSpec(key="phone",     label="Phone",     required=False, col_type="text"),
        ],
        handler=my_lead_import_handler,
    ))

Handler signature::

    def my_lead_import_handler(
        row_data: dict[str, Any],
        *,
        tenant,
        actor,
        organization_node_id: int,
    ) -> None:
        ...
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Callable

__all__ = [
    "ColumnSpec",
    "ImportSpec",
    "ImportRegistry",
    "import_registry",
]


@dataclass(frozen=True, slots=True)
class ColumnSpec:
    """Describes a single importable column.

    ``col_type`` governs validation:
    - ``"text"``    — any non-empty string
    - ``"email"``   — must match basic email pattern
    - ``"number"``  — must parse as float
    - ``"integer"`` — must parse as int
    - ``"boolean"`` — "true"/"false"/"1"/"0"/"yes"/"no"
    - ``"date"``    — ISO-8601 date (YYYY-MM-DD)
    """

    key: str
    label: str
    required: bool = False
    col_type: str = "text"


@dataclass
class ImportSpec:
    """Schema + handler for one entity type."""

    entity_type: str
    columns: list[ColumnSpec]
    handler: Callable[..., None]

    # Optional human-readable display name (falls back to entity_type)
    display_name: str = ""

    def column_by_key(self, key: str) -> ColumnSpec | None:
        for col in self.columns:
            if col.key == key:
                return col
        return None

    def required_keys(self) -> list[str]:
        return [c.key for c in self.columns if c.required]

    def all_keys(self) -> list[str]:
        return [c.key for c in self.columns]


class ImportRegistry:
    """Singleton registry that maps entity_type → ImportSpec."""

    def __init__(self) -> None:
        self._specs: dict[str, ImportSpec] = {}

    # ------------------------------------------------------------------
    # Registration
    # ------------------------------------------------------------------

    def register(self, spec: ImportSpec) -> None:
        """Register *spec*.  Raises ``ValueError`` if already registered."""
        if spec.entity_type in self._specs:
            raise ValueError(
                f"ImportSpec for entity type {spec.entity_type!r} is already registered. "
                "Use replace() if intentional."
            )
        self._specs[spec.entity_type] = spec

    def replace(self, spec: ImportSpec) -> None:
        """Register or replace the spec for *entity_type*."""
        self._specs[spec.entity_type] = spec

    def unregister(self, entity_type: str) -> None:
        """Remove *entity_type* from registry.  No-op if not registered."""
        self._specs.pop(entity_type, None)

    # ------------------------------------------------------------------
    # Lookup
    # ------------------------------------------------------------------

    def get(self, entity_type: str) -> ImportSpec | None:
        return self._specs.get(entity_type)

    def get_or_raise(self, entity_type: str) -> ImportSpec:
        spec = self.get(entity_type)
        if spec is None:
            raise KeyError(
                f"No ImportSpec registered for entity type {entity_type!r}. "
                f"Available: {sorted(self._specs)}"
            )
        return spec

    def all_entity_types(self) -> list[str]:
        return sorted(self._specs)

    def all_specs(self) -> list[ImportSpec]:
        return [self._specs[k] for k in sorted(self._specs)]

    def __contains__(self, entity_type: str) -> bool:
        return entity_type in self._specs

    def __len__(self) -> int:
        return len(self._specs)

    # ------------------------------------------------------------------
    # Row validation helpers
    # ------------------------------------------------------------------

    def validate_row(
        self,
        spec: ImportSpec,
        row: dict[str, Any],
        row_number: int,
    ) -> list[str]:
        """Validate *row* against *spec*.  Returns a list of error strings."""
        import re

        errors: list[str] = []

        for col in spec.columns:
            raw = row.get(col.key)
            missing = raw is None or str(raw).strip() == ""

            if col.required and missing:
                errors.append(f"Column '{col.label}' is required (row {row_number}).")
                continue

            if missing:
                continue  # optional, skip type check

            value = str(raw).strip()

            if col.col_type == "email":
                if not re.fullmatch(r"[^@\s]+@[^@\s]+\.[^@\s]+", value):
                    errors.append(
                        f"Column '{col.label}' must be a valid email (row {row_number})."
                    )
            elif col.col_type == "number":
                try:
                    float(value)
                except ValueError:
                    errors.append(
                        f"Column '{col.label}' must be a number (row {row_number})."
                    )
            elif col.col_type == "integer":
                try:
                    int(value)
                except ValueError:
                    errors.append(
                        f"Column '{col.label}' must be an integer (row {row_number})."
                    )
            elif col.col_type == "boolean":
                if value.lower() not in ("true", "false", "1", "0", "yes", "no"):
                    errors.append(
                        f"Column '{col.label}' must be a boolean "
                        f"(true/false/1/0/yes/no) (row {row_number})."
                    )
            elif col.col_type == "date":
                import datetime as _dt

                try:
                    _dt.date.fromisoformat(value)
                except ValueError:
                    errors.append(
                        f"Column '{col.label}' must be an ISO date "
                        f"(YYYY-MM-DD) (row {row_number})."
                    )

        return errors


# Module-level singleton
import_registry = ImportRegistry()
