"""DMS repositories bounded context — models.

Entities
--------
Repository
    Top-level named container for a set of DMS documents.
    Can be scoped to a specific workspace (optional) or be tenant-wide.
    Carries a storage quota and exposes a unique slug per tenant.

Folder
    Hierarchical node inside a Repository.
    Uses an adjacency-list design (parent FK) augmented with a stored
    ``materialized_path`` column (UUID ancestry chain) so that subtree
    queries never require recursive SQL — a single
    ``startswith(path + "/")`` LIKE is sufficient.

Materialized-path convention
    Root folder A:  ``materialized_path = str(A.public_id)``
    Child B of A:   ``materialized_path = str(A.public_id) + "/" + str(B.public_id)``
    Grandchild C:   ``str(A.public_id) + "/" + str(B.public_id) + "/" + str(C.public_id)``

    Separator is ``/`` (safe: the field contains hex UUIDs only).
    ``depth`` mirrors ``len(path.split("/")) - 1`` but is stored for
    efficient ordering and display queries.
"""

from __future__ import annotations

from typing import ClassVar

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


# ---------------------------------------------------------------------------
# Repository
# ---------------------------------------------------------------------------

class Repository(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """A named document repository scoped to a tenant (+ optionally a workspace).

    Every DMS document lives inside exactly one repository.  Multiple
    repositories allow different governance rules, metadata schemas, and ACLs
    to coexist within the same tenant.

    Workspace scoping is optional: when ``workspace`` is NULL the repository
    is visible to all workspaces of the tenant; when set, only members of
    that workspace see it (Phase 5 ACL will refine this further).
    """

    name = models.CharField(_("name"), max_length=200)
    slug = models.SlugField(
        _("slug"),
        max_length=120,
        help_text=_("URL-safe identifier, unique per tenant. E.g. 'hr-documents'."),
    )
    description = models.TextField(_("description"), blank=True, default="")

    # Optional workspace scoping ----------------------------------------
    workspace = models.ForeignKey(
        "platform_workspaces.Workspace",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="dms_repositories",
        verbose_name=_("workspace"),
        help_text=_("Leave blank for a tenant-wide repository."),
    )

    # Flags / metadata ---------------------------------------------------
    is_default = models.BooleanField(
        _("default"),
        default=False,
        help_text=_("Marks the tenant's primary repository."),
    )
    max_size_bytes = models.BigIntegerField(
        _("max size bytes"),
        null=True,
        blank=True,
        help_text=_("Storage quota for this repository. NULL = unlimited."),
    )

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("repository")
        verbose_name_plural = _("repositories")
        ordering = ("name",)
        constraints = [
            models.UniqueConstraint(
                fields=("tenant", "slug"),
                condition=models.Q(is_deleted=False),
                name="dms_repository_unique_slug_per_tenant",
            ),
        ]
        indexes = [
            models.Index(fields=("tenant", "is_deleted")),
            models.Index(fields=("tenant", "workspace", "is_deleted")),
        ]

    def __str__(self) -> str:
        return f"{self.name} ({self.slug})"


# ---------------------------------------------------------------------------
# Folder
# ---------------------------------------------------------------------------

class Folder(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """A hierarchical logical folder inside a :class:`Repository`.

    Folder trees use the **adjacency-list + materialized-path** hybrid:

    * ``parent`` FK — the direct parent (NULL for root folders).
    * ``materialized_path`` — stored ancestry UUID chain; enables O(1) path
      lookup and O(n_descendants) subtree queries without recursive SQL.
    * ``depth`` — 0 for root; mirrors path depth; stored for performance.

    Moving a folder updates ``materialized_path`` and ``depth`` for the
    folder itself and all its descendants (handled in ``services.move_folder``).
    """

    repository = models.ForeignKey(
        Repository,
        on_delete=models.CASCADE,
        related_name="folders",
        verbose_name=_("repository"),
    )
    parent = models.ForeignKey(
        "self",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="children",
        verbose_name=_("parent folder"),
    )
    name = models.CharField(_("name"), max_length=255)

    # Materialized path (UUID-based, stable across renames) ---------------
    materialized_path = models.CharField(
        _("materialized path"),
        max_length=4096,
        blank=True,
        default="",
        help_text=_(
            "Ancestry chain using public_id UUIDs: 'uuid1/uuid2/.../own_uuid'. "
            "Updated automatically on create/move. Never set manually."
        ),
    )
    depth = models.PositiveIntegerField(
        _("depth"),
        default=0,
        help_text=_("0 = root folder, 1 = one level down, etc."),
    )

    # UX / display --------------------------------------------------------
    sort_order = models.IntegerField(_("sort order"), default=0)
    color = models.CharField(_("color"), max_length=32, blank=True, default="")
    icon = models.CharField(_("icon"), max_length=64, blank=True, default="")

    # System flag (auto-managed folders not editable by end-users) --------
    is_system = models.BooleanField(_("system folder"), default=False)

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("folder")
        verbose_name_plural = _("folders")
        ordering = ("sort_order", "name")
        indexes = [
            models.Index(fields=("repository", "parent", "is_deleted")),
            models.Index(fields=("repository", "materialized_path")),
            models.Index(fields=("tenant", "is_deleted")),
        ]

    def __str__(self) -> str:
        return self.name

    @property
    def full_path(self) -> str:
        """Human-readable path computed by traversing parents in Python.

        Prefer the ``get_ancestors`` query helper when you need the full
        ancestor chain from the DB.  This property is only suitable for
        single-object display.
        """
        parts: list[str] = []
        node: Folder | None = self
        while node is not None:
            parts.append(node.name)
            # Only traverse loaded parent to avoid N+1 outside of tree operations.
            node = node.parent if node.parent_id else None
        return "/" + "/".join(reversed(parts))
