"""Core API serializers — tenant-aware DRF serializer base classes.

The key design rule: ``tenant_id`` (and ``organization_node_id``) must
**never** be accepted as input from the API client — they are always
resolved from ``request.tenant`` (injected by TenantMiddleware) and from
the authenticated user's membership context.

Usage::

    from simorgh.core.api.serializers import TenantScopedSerializer
    from rest_framework import serializers
    from myapp.models import MyModel

    class MySerializer(TenantScopedSerializer):
        name = serializers.CharField()

        class Meta:
            model = MyModel
            fields = ("id", "name", "created_at")

    # In a view:
    ser = MySerializer(data=request.data, context={"request": request})
    ser.is_valid(raise_exception=True)
    obj = ser.save()    # calls create() with tenant injected automatically
"""
from __future__ import annotations

from typing import Any

from rest_framework import serializers

__all__ = [
    "TenantScopedSerializer",
    "PublicIdMixin",
    "ReadOnlyTimestampsMixin",
]


class PublicIdMixin(serializers.Serializer):
    """Expose the UUID ``public_id`` field as ``id`` in the serialized output.

    Mix in before the concrete serializer class::

        class MySerializer(PublicIdMixin, serializers.ModelSerializer): ...
    """

    id = serializers.UUIDField(source="public_id", read_only=True)


class ReadOnlyTimestampsMixin(serializers.Serializer):
    """Expose ``created_at`` / ``updated_at`` as ISO 8601 read-only fields."""

    created_at = serializers.DateTimeField(read_only=True)
    updated_at = serializers.DateTimeField(read_only=True)


class TenantScopedSerializer(serializers.ModelSerializer):
    """ModelSerializer that injects ``tenant`` from the request context.

    Rules
    -----
    * ``tenant`` is **never** in ``fields`` — it is always set via
      :meth:`create` from ``context["request"].tenant``.
    * ``organization_node_id`` follows the same principle when
      ``inject_org_node = True`` is set on the serializer class.

    Raises ``serializers.ValidationError`` (HTTP 400) when the request has
    no tenant, so views do not need to guard this themselves.

    Extra class attributes
    ----------------------
    inject_org_node : bool
        When ``True`` (default ``False``), also inject ``organization_node_id``
        from the first org node in the request context.  Override
        :meth:`get_organization_node_id` for custom resolution.
    """

    inject_org_node: bool = False

    # -----------------------------------------------------------------------
    # Internal helpers
    # -----------------------------------------------------------------------

    def _get_tenant(self):
        request = self.context.get("request")
        if request is None:
            raise serializers.ValidationError(
                {"non_field_errors": ["Serializer context must include 'request'."]}
            )
        tenant = getattr(request, "tenant", None)
        if tenant is None:
            raise serializers.ValidationError(
                {"non_field_errors": ["Tenant not found on request."]}
            )
        return tenant

    def get_organization_node_id(self) -> int | None:
        """Return the org node id to inject, or ``None``.

        Default: uses the first org node from the request context (if any).
        Override for custom resolution logic.
        """
        from simorgh.core.context import current_request_context
        ctx = current_request_context()
        if ctx.org_node_ids:
            return next(iter(ctx.org_node_ids))
        return None

    # -----------------------------------------------------------------------
    # DRF overrides
    # -----------------------------------------------------------------------

    def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
        # Remove any client-supplied tenant/org values (security guard)
        attrs.pop("tenant", None)
        attrs.pop("tenant_id", None)
        attrs.pop("organization_node_id", None)
        return super().validate(attrs)

    def create(self, validated_data: dict[str, Any]) -> Any:
        tenant = self._get_tenant()
        validated_data["tenant"] = tenant
        if self.inject_org_node:
            node_id = self.get_organization_node_id()
            if node_id is not None:
                validated_data.setdefault("organization_node_id", node_id)
        return super().create(validated_data)

    def update(self, instance: Any, validated_data: dict[str, Any]) -> Any:
        # Prevent tenant from being changed on update
        validated_data.pop("tenant", None)
        validated_data.pop("tenant_id", None)
        return super().update(instance, validated_data)
