"""DMS documents bounded context — models.

Entities
--------
DocumentType
    Tenant-scoped categorisation schema (e.g. "Contract", "Invoice").
    Lightweight — acts as a configuration master.

Document
    The logical DMS document.  A document is the **identity** that lives
    across its entire lifetime and holds a reference to the *current*
    published version.

    A document without any version is a "shell" (useful when first registering
    a document before uploading content).

DocumentVersion
    An immutable snapshot of a document at a point in time.
    Each version carries:
      - a reference to the underlying file asset (FileMetadata)
      - a semantic version number (major.minor)
      - a lifecycle status (DRAFT → PUBLISHED → SUPERSEDED / ARCHIVED)

    The version history is append-only.  Once published a version is never
    mutated — only its status changes when superseded by a newer version.

Versioning convention
    First version:    1.0
    Minor bump:       1.0 → 1.1 → 1.2 …
    Major bump:       1.2 → 2.0
    Rollback:         always creates a NEW version (copy); does not rewind.

Document lifecycle
    DRAFT      — no published version yet (may have draft versions)
    PUBLISHED  — current_version is published
    ARCHIVED   — document removed from active use; no new versions accepted
"""

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


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class DocumentStatus(models.TextChoices):
    DRAFT = "draft", _("Draft")
    PUBLISHED = "published", _("Published")
    ARCHIVED = "archived", _("Archived")


class DocumentVersionStatus(models.TextChoices):
    DRAFT = "draft", _("Draft")
    PUBLISHED = "published", _("Published")
    SUPERSEDED = "superseded", _("Superseded")
    ARCHIVED = "archived", _("Archived")


class VersionBump(models.TextChoices):
    MINOR = "minor", _("Minor")
    MAJOR = "major", _("Major")


# ---------------------------------------------------------------------------
# DocumentType
# ---------------------------------------------------------------------------

class DocumentType(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """A tenant-scoped document category (e.g. Contract, Invoice, Policy).

    ``code`` acts as the stable machine identifier and must remain unique per
    tenant among active (non-deleted) types.  ``name`` is the display label
    shown in the UI.
    """

    name = models.CharField(_("name"), max_length=200)
    code = models.SlugField(
        _("code"),
        max_length=80,
        help_text=_("URL-safe identifier, unique per tenant.  E.g. 'contract'."),
    )
    description = models.TextField(_("description"), blank=True, default="")
    icon = models.CharField(_("icon"), max_length=64, blank=True, default="")
    color = models.CharField(_("color"), max_length=32, blank=True, default="")
    is_active = models.BooleanField(_("active"), default=True)

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("document type")
        verbose_name_plural = _("document types")
        ordering = ("name",)
        constraints = [
            models.UniqueConstraint(
                fields=("tenant", "code"),
                condition=models.Q(is_deleted=False),
                name="dms_documenttype_unique_code_per_tenant",
            ),
        ]
        indexes = [
            models.Index(fields=("tenant", "is_active", "is_deleted")),
        ]

    def __str__(self) -> str:
        return f"{self.name} ({self.code})"


# ---------------------------------------------------------------------------
# Document
# ---------------------------------------------------------------------------

class Document(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """The canonical logical document entity.

    A document persists across its entire lifecycle and owns a stable identity
    (``public_id``, ``code``).  The *content* lives in ``DocumentVersion``
    rows; ``current_version`` points to the latest active (published or draft)
    version.

    Hierarchy
    ---------
    Every document must belong to a ``Repository``.  Placement inside a
    ``Folder`` is optional; a NULL ``folder`` means the document sits at the
    repository root.

    Metadata extensibility
    ----------------------
    ``extra`` is a JSONField reserved for Phase-4 metadata values that do not
    yet have their own MetadataValue rows.  Services must never rely on
    arbitrary keys inside ``extra``; it is a pass-through store for future
    phases.

    Workflow hooks
    --------------
    ``workflow_status`` is a free-text field managed by an external workflow
    engine.  The DMS layer only stores and surfaces it — no state machine here.
    """

    title = models.CharField(_("title"), max_length=500)
    code = models.CharField(
        _("code"),
        max_length=100,
        blank=True,
        default="",
        help_text=_(
            "Optional human-readable document number, unique per tenant "
            "among active documents.  E.g. 'CONTRACT-2024-001'."
        ),
    )

    document_type = models.ForeignKey(
        DocumentType,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="documents",
        verbose_name=_("document type"),
    )

    # Hierarchy ----------------------------------------------------------------
    repository = models.ForeignKey(
        "dms.Repository",
        on_delete=models.CASCADE,
        related_name="documents",
        verbose_name=_("repository"),
    )
    folder = models.ForeignKey(
        "dms.Folder",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="documents",
        verbose_name=_("folder"),
    )

    # Lifecycle ----------------------------------------------------------------
    status = models.CharField(
        _("status"),
        max_length=20,
        choices=DocumentStatus.choices,
        default=DocumentStatus.DRAFT,
        db_index=True,
    )

    # Current version pointer --------------------------------------------------
    # NULL until the first version is created.  Updated by services whenever
    # a version is published or a new draft is created.
    current_version = models.OneToOneField(
        "dms.DocumentVersion",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="current_for_document",
        verbose_name=_("current version"),
    )

    # Workflow hook (opaque string set by external workflow engine) ------------
    workflow_status = models.CharField(
        _("workflow status"),
        max_length=100,
        blank=True,
        default="",
    )

    # Extensibility hooks (Phase 4 will introduce proper MetadataValues) ------
    extra = models.JSONField(
        _("extra"),
        default=dict,
        blank=True,
        help_text=_(
            "Pass-through JSON store for future metadata phases.  "
            "Do not put business logic on these keys."
        ),
    )

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("document")
        verbose_name_plural = _("documents")
        ordering = ("-created_at",)
        constraints = [
            models.UniqueConstraint(
                fields=("tenant", "code"),
                condition=models.Q(is_deleted=False) & ~models.Q(code=""),
                name="dms_document_unique_code_per_tenant",
            ),
        ]
        indexes = [
            models.Index(fields=("tenant", "status", "is_deleted")),
            models.Index(fields=("repository", "folder", "is_deleted")),
            models.Index(fields=("tenant", "document_type", "is_deleted")),
        ]

    def __str__(self) -> str:
        return self.title


# ---------------------------------------------------------------------------
# DocumentVersion
# ---------------------------------------------------------------------------

class DocumentVersion(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """An immutable content snapshot of a :class:`Document`.

    Rules
    -----
    * Once a version reaches PUBLISHED status its ``file_asset`` must not be
      swapped.  Enforcement is handled in the service layer.
    * ``version_major`` and ``version_minor`` together form the display version
      number (e.g. ``2.3``).  They are set by the service when the version is
      created and never changed after that.
    * ``is_current`` is a denormalized flag that mirrors
      ``document.current_version == self``.  It is kept in sync by services
      and exists to make listing queries (show all versions, highlight current)
      cheaper.

    Mirrored file fields
    --------------------
    ``file_size_bytes``, ``content_type``, and ``checksum_sha256`` are copied
    from the linked ``FileMetadata`` at version-creation time.  This avoids
    joins when rendering version lists and protects against FileMetadata
    mutations.
    """

    document = models.ForeignKey(
        Document,
        on_delete=models.CASCADE,
        related_name="versions",
        verbose_name=_("document"),
    )

    # File content ----------------------------------------------------------------
    file_asset = models.ForeignKey(
        "storage.FileMetadata",
        on_delete=models.PROTECT,
        related_name="document_versions",
        verbose_name=_("file asset"),
        null=True,
        blank=True,
        help_text=_(
            "The underlying file.  May be NULL for a shell version created "
            "before the file upload completes."
        ),
    )

    # Mirrored from FileMetadata for display efficiency ----------------------
    file_size_bytes = models.BigIntegerField(
        _("file size bytes"), null=True, blank=True
    )
    content_type = models.CharField(
        _("content type"), max_length=200, blank=True, default=""
    )
    checksum_sha256 = models.CharField(
        _("checksum SHA-256"), max_length=64, blank=True, default=""
    )

    # Version number -----------------------------------------------------------
    version_major = models.PositiveIntegerField(_("major version"), default=1)
    version_minor = models.PositiveIntegerField(_("minor version"), default=0)

    # Lifecycle ----------------------------------------------------------------
    status = models.CharField(
        _("status"),
        max_length=20,
        choices=DocumentVersionStatus.choices,
        default=DocumentVersionStatus.DRAFT,
        db_index=True,
    )
    is_current = models.BooleanField(
        _("is current"),
        default=False,
        help_text=_("Denormalized: mirrors document.current_version == self."),
    )

    # Human annotations --------------------------------------------------------
    label = models.CharField(
        _("label"), max_length=200, blank=True, default="",
        help_text=_("Optional display label, e.g. 'Initial release'."),
    )
    change_summary = models.TextField(
        _("change summary"), blank=True, default="",
        help_text=_("What changed in this version."),
    )

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("document version")
        verbose_name_plural = _("document versions")
        ordering = ("-version_major", "-version_minor")
        constraints = [
            models.UniqueConstraint(
                fields=("document", "version_major", "version_minor"),
                condition=models.Q(is_deleted=False),
                name="dms_documentversion_unique_version_per_document",
            ),
        ]
        indexes = [
            models.Index(fields=("document", "status", "is_deleted")),
            models.Index(fields=("tenant", "is_deleted")),
        ]

    def __str__(self) -> str:
        return f"{self.document} v{self.version_major}.{self.version_minor}"

    @property
    def version_label(self) -> str:
        return f"{self.version_major}.{self.version_minor}"
