"""DMS search bounded context — models.

Entities
--------
DocumentSearchIndex
    Denormalized, always-current search snapshot of a Document.
    Updated by ``index_document()`` whenever a Document or its current
    DocumentVersion changes.  Designed to be mirrored to OpenSearch /
    Elasticsearch in production without changing the API contract.

    Key design decisions:
    * One row per live Document (OneToOneField).
    * ``search_text`` is the concatenated plain-text FTS blob built from
      title, code, document-type name, version label, and file name.
    * ``tags`` is a JSONField list so tag filtering works on both SQLite
      (dev/test) and PostgreSQL (prod).  A PostgreSQL ArrayField can replace
      it later without changing the service layer.
    * ``extra_json`` is reserved for Phase-4 structured metadata indexing.
    * ``index_version`` is a monotonic integer for detecting stale replicas
      when integrating an external search backend.

SavedSearch
    A user-owned, optionally team-shared persisted search query.
    ``query_params`` stores the serialized SearchParams dict validated by
    ``SearchParamsSerializer``.
"""

from __future__ import annotations

from typing import ClassVar

from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _

from simorgh.core.db.scoped import ScopedSoftDeleteManager
from simorgh.core.models import AuditedModel, SoftDeleteModel, TenantScopedModel, UUIDModel


# ---------------------------------------------------------------------------
# DocumentSearchIndex
# ---------------------------------------------------------------------------

class DocumentSearchIndex(UUIDModel, TenantScopedModel, AuditedModel):
    """Denormalized search read-model — one row per live Document.

    No SoftDeleteModel: index entries are replaced or hard-deleted by services;
    they are never soft-deleted independently of their parent Document.
    """

    document = models.OneToOneField(
        "dms.Document",
        on_delete=models.CASCADE,
        related_name="search_index",
        verbose_name=_("document"),
    )

    # --- Denormalized document fields ----------------------------------------

    title = models.TextField(_("title"), default="", blank=True)
    code = models.CharField(_("code"), max_length=100, blank=True, default="")
    document_type_code = models.CharField(
        _("document type code"), max_length=80, blank=True, default=""
    )
    document_type_name = models.CharField(
        _("document type name"), max_length=200, blank=True, default=""
    )
    repository_public_id = models.UUIDField(
        _("repository public id"), null=True, blank=True, db_index=True
    )
    folder_public_id = models.UUIDField(
        _("folder public id"), null=True, blank=True, db_index=True
    )
    status = models.CharField(
        _("status"), max_length=20, blank=True, default="", db_index=True
    )
    workflow_status = models.CharField(
        _("workflow status"), max_length=100, blank=True, default=""
    )

    # --- File info mirrored from current DocumentVersion ---------------------

    file_name = models.CharField(_("file name"), max_length=500, blank=True, default="")
    content_type = models.CharField(
        _("content type"), max_length=200, blank=True, default="", db_index=True
    )
    file_size = models.BigIntegerField(_("file size bytes"), null=True, blank=True)
    version_label = models.CharField(
        _("version label"), max_length=50, blank=True, default=""
    )

    # --- Full-text blob -------------------------------------------------------

    search_text = models.TextField(
        _("search text"),
        blank=True,
        default="",
        help_text=_(
            "Concatenated searchable plain text: title + code + document-type "
            "name + version label + file name.  Populated by index_document()."
        ),
    )

    # --- Structured extras ---------------------------------------------------

    tags = models.JSONField(
        _("tags"),
        default=list,
        help_text=_(
            "Lowercase string labels, e.g. [\"contract\", \"urgent\"].  "
            "Populated from document.extra[\"tags\"] or AI classification."
        ),
    )
    extra_json = models.JSONField(
        _("extra JSON"),
        default=dict,
        blank=True,
        help_text=_(
            "Reserved for Phase-4 structured metadata indexing.  "
            "Arbitrary key/value pairs added by the metadata engine."
        ),
    )

    # --- Index bookkeeping ---------------------------------------------------

    indexed_at = models.DateTimeField(_("indexed at"), auto_now=True)
    index_version = models.PositiveIntegerField(_("index version"), default=1)

    class Meta:
        verbose_name = _("document search index")
        verbose_name_plural = _("document search indices")
        indexes = [
            models.Index(
                fields=["tenant", "status"],
                name="dms_srch_tenant_status_idx",
            ),
            models.Index(
                fields=["tenant", "content_type"],
                name="dms_srch_tenant_ctype_idx",
            ),
            models.Index(
                fields=["tenant", "document_type_code"],
                name="dms_srch_tenant_dtype_idx",
            ),
            models.Index(
                fields=["tenant", "indexed_at"],
                name="dms_srch_tenant_idxat_idx",
            ),
        ]

    def __str__(self) -> str:  # pragma: no cover
        return f"SearchIndex({self.title!r})"


# ---------------------------------------------------------------------------
# SavedSearch
# ---------------------------------------------------------------------------

class SavedSearch(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """A user-owned, optionally team-shared persisted search query.

    ``query_params`` mirrors the fields of ``SearchParams`` and is validated
    at write time by ``SearchParamsSerializer``.

    When ``is_shared=True`` the entry is readable by all active tenant users
    but only the owner (or a superuser) may modify or delete it.
    """

    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="dms_saved_searches",
        verbose_name=_("owner"),
    )
    name = models.CharField(_("name"), max_length=200)
    description = models.TextField(_("description"), blank=True, default="")
    query_params = models.JSONField(
        _("query params"),
        default=dict,
        help_text=_(
            "Serialized SearchParams dict.  Validated by SearchParamsSerializer "
            "at create/update time."
        ),
    )
    is_shared = models.BooleanField(
        _("shared"),
        default=False,
        help_text=_(
            "When True this saved search is visible (read-only) to all "
            "active users in the same tenant."
        ),
    )
    last_used_at = models.DateTimeField(_("last used at"), null=True, blank=True)
    use_count = models.PositiveIntegerField(_("use count"), default=0)

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("saved search")
        verbose_name_plural = _("saved searches")
        ordering = ("-created_at",)
        indexes = [
            models.Index(
                fields=["tenant", "owner", "is_deleted"],
                name="dms_savedsearch_owner_idx",
            ),
            models.Index(
                fields=["tenant", "is_shared", "is_deleted"],
                name="dms_savedsearch_shared_idx",
            ),
        ]

    def __str__(self) -> str:  # pragma: no cover
        return f"SavedSearch({self.name!r})"
