"""DMS records bounded context — models.

Design
------
Phase 10 introduces three entities that form the *records management layer* —
governance and compliance primitives for enterprise document lifecycle control.

RetentionPolicy
    A tenant-level policy that defines how long documents should be retained
    and what action to take when the retention period expires.

    ``retention_period_days`` is the canonical period.  Zero means "keep
    forever until explicitly purged".

    ``action_on_expiry`` defines the post-expiry workflow:
      - REVIEW  — flag for manual review; no automatic action
      - ARCHIVE — automatically archive the document
      - DESTROY — mark for destruction (requires a separate process/approval)

    Soft-deletable.  A deleted policy is no longer assignable but existing
    ArchiveRecords that reference it retain the FK (SET_NULL not applicable
    here since we want history — we use PROTECT on ArchiveRecord).

LegalHold
    A factual record that places a compliance hold on a specific Document,
    preventing its deletion or destruction.

    Legal holds are NOT soft-deleted — they are an immutable audit trail.
    ``ended_at=None`` means the hold is still active.

    Multiple concurrent holds on a single document are allowed (e.g. two
    separate litigation cases).  A document is considered held as long as
    at least one hold has ``ended_at=None``.

    Releasing a hold sets ``ended_at`` and ``released_by``; it does not
    delete the record.

ArchiveRecord
    An immutable record of an archiving event for a Document/DocumentVersion.

    Once created it cannot be updated.  ``archived_at`` is auto-set.
    If a ``retention_policy`` is provided the service layer computes
    ``expires_at = archived_at + retention_period_days``.

    ``is_permanent=True`` means no expiry regardless of policy; in this
    case ``expires_at`` is always None.

    The FK to ``document`` uses PROTECT so that an archived document
    cannot be accidentally hard-deleted as long as an ArchiveRecord
    references it.

Immutability guarantee
    No update service exists for ArchiveRecord.  The model intentionally
    has no ``is_deleted`` field.  Archive records survive document soft-deletes.

Tamper resistance
    Archive records are write-once.  The service layer enforces this.
    Future phases can add cryptographic hash fields for additional tamper
    evidence.

Legal hold guard
    The ``check_legal_hold`` query/service helper is intentionally public so
    that other bounded contexts (e.g. a delete service in documents/) can
    consult it before performing destructive operations.
"""

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


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class ActionOnExpiry(models.TextChoices):
    REVIEW = "review", _("Review")
    ARCHIVE = "archive", _("Archive")
    DESTROY = "destroy", _("Destroy")


# ---------------------------------------------------------------------------
# RetentionPolicy
# ---------------------------------------------------------------------------

class RetentionPolicy(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """Defines a tenant-scoped retention rule.

    Policies are referenced by ArchiveRecords to determine lifecycle
    expiry dates.  A policy that is soft-deleted can no longer be assigned
    to new archive records but existing references are preserved.
    """

    name = models.CharField(_("name"), max_length=200)
    description = models.TextField(_("description"), blank=True, default="")
    retention_period_days = models.PositiveIntegerField(
        _("retention period (days)"),
        default=0,
        help_text=_("0 = keep indefinitely until explicit action."),
    )
    action_on_expiry = models.CharField(
        _("action on expiry"),
        max_length=20,
        choices=ActionOnExpiry.choices,
        default=ActionOnExpiry.REVIEW,
    )
    is_active = models.BooleanField(_("active"), default=True)

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("retention policy")
        verbose_name_plural = _("retention policies")
        ordering = ("name",)
        constraints = [
            models.UniqueConstraint(
                fields=("tenant", "name"),
                condition=models.Q(is_deleted=False),
                name="dms_retentionpolicy_unique_name_per_tenant",
            ),
        ]
        indexes = [
            models.Index(
                fields=("tenant", "is_active", "is_deleted"),
                name="dms_reten_tenant_act_idx",
            ),
        ]

    def __str__(self) -> str:
        return f"{self.name} ({self.retention_period_days}d)"


# ---------------------------------------------------------------------------
# LegalHold
# ---------------------------------------------------------------------------

class LegalHold(UUIDModel, TenantScopedModel, AuditedModel):
    """A compliance hold placed on a document.

    Not soft-deletable — legal holds are factual records that must be
    retained for audit purposes.  Releasing a hold ends the hold period
    (sets ``ended_at``) but does not remove the record.
    """

    document = models.ForeignKey(
        "dms.Document",
        on_delete=models.CASCADE,
        related_name="legal_holds",
        verbose_name=_("document"),
    )
    name = models.CharField(
        _("hold name"),
        max_length=200,
        help_text=_("Case name or brief description of the hold reason."),
    )
    notes = models.TextField(_("notes"), blank=True, default="")
    placed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="placed_legal_holds",
        verbose_name=_("placed by"),
    )
    released_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="released_legal_holds",
        verbose_name=_("released by"),
    )
    started_at = models.DateTimeField(_("started at"), auto_now_add=True)
    ended_at = models.DateTimeField(
        _("ended at"),
        null=True,
        blank=True,
        help_text=_("Null means the hold is still active."),
    )

    class Meta:
        verbose_name = _("legal hold")
        verbose_name_plural = _("legal holds")
        ordering = ("-started_at",)
        indexes = [
            models.Index(
                fields=("document",),
                name="dms_legalhold_doc_idx",
            ),
            models.Index(
                fields=("document", "ended_at"),
                name="dms_legalhold_doc_active_idx",
            ),
            models.Index(
                fields=("tenant", "ended_at"),
                name="dms_lhld_tenant_act_idx",
            ),
        ]

    @property
    def is_active(self) -> bool:
        return self.ended_at is None

    def __str__(self) -> str:
        status = "active" if self.is_active else "released"
        return f"LegalHold({self.name!r}, {status})"


# ---------------------------------------------------------------------------
# ArchiveRecord
# ---------------------------------------------------------------------------

class ArchiveRecord(UUIDModel, TenantScopedModel, AuditedModel):
    """An immutable record of a document archiving event.

    Write-once: once created it must not be modified.  The service layer
    enforces this by providing no update function.

    ``document`` is PROTECT so that an archived document cannot be
    hard-deleted while an archive record exists.

    ``version`` is SET_NULL so a specific version can be referenced
    optionally; NULL means the entire document was archived rather than
    a specific version.

    ``expires_at`` is computed at creation time from the retention policy.
    NULL means either ``is_permanent=True`` or no policy / zero-day policy.
    """

    document = models.ForeignKey(
        "dms.Document",
        on_delete=models.PROTECT,
        related_name="archive_records",
        verbose_name=_("document"),
    )
    version = models.ForeignKey(
        "dms.DocumentVersion",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="archive_records",
        verbose_name=_("document version"),
    )
    retention_policy = models.ForeignKey(
        RetentionPolicy,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="archive_records",
        verbose_name=_("retention policy"),
    )
    archived_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="archive_records",
        verbose_name=_("archived by"),
    )
    archived_at = models.DateTimeField(_("archived at"))
    reason = models.TextField(_("reason"), blank=True, default="")
    expires_at = models.DateTimeField(
        _("expires at"),
        null=True,
        blank=True,
        help_text=_(
            "Computed from archived_at + retention_period_days.  "
            "Null if is_permanent=True or policy has no expiry."
        ),
    )
    is_permanent = models.BooleanField(
        _("permanent"),
        default=False,
        help_text=_("If True, expires_at is always null regardless of policy."),
    )

    class Meta:
        verbose_name = _("archive record")
        verbose_name_plural = _("archive records")
        ordering = ("-archived_at",)
        indexes = [
            models.Index(
                fields=("document",),
                name="dms_archive_doc_idx",
            ),
            models.Index(
                fields=("document", "archived_at"),
                name="dms_archive_doc_date_idx",
            ),
            models.Index(
                fields=("tenant", "expires_at"),
                name="dms_archive_tenant_expires_idx",
            ),
        ]

    def __str__(self) -> str:
        return f"ArchiveRecord(doc={self.document_id}, at={self.archived_at})"
