"""DMS collaboration bounded context — models.

Design
------
Phase 9 introduces three entities that together form the *collaboration layer*:

Comment
    A text note attached to a Document (and optionally to a specific
    DocumentVersion).  Comments support *threading* via a self-referential
    ``parent`` FK: top-level posts have ``parent=None``; replies point to the
    root comment (not to their immediate parent), keeping the tree flat (max
    one level of nesting is enforced by the service layer for simplicity, but
    the schema is open to deeper threading).

    Comments have a *resolve* lifecycle: any user with the resolve permission
    can mark a thread as resolved (``is_resolved=True``).  Resolved comments
    are still readable; they are not deleted.

    Soft-delete is supported via ``ScopedSoftDeleteManager``.  Deleted comments
    are hidden from API consumers but retained for audit purposes.

Annotation
    A position-aware mark on a ``DocumentVersion`` — typically used for PDF
    highlights, sticky notes, freehand drawings, or stamps.

    ``position_data`` is a freeform JSONField so the front-end (or a third-
    party viewer SDK) can store any coordinate system without a schema change.
    A canonical sub-schema for PDF viewers is::

        {
            "page": 3,
            "x": 0.12, "y": 0.45,   # relative 0-1 within the page
            "width": 0.30, "height": 0.05,
            "rects": [[...], ...]    # optional word-level highlight rects
        }

    ``document`` is denormalised from ``version.document`` for efficient
    single-document queries (avoids a join through DocumentVersion).

    An annotation can optionally be *linked* to a Comment so that a discussion
    thread can be attached to a specific mark-up.

Mention
    A lightweight junction table that records which users were @mentioned
    in a comment body.  ``is_notified`` is a flag for the notification
    pipeline — when the DMS calls the notification engine (Phase 12 / external)
    it can flip this flag to avoid duplicate sends.

    Mentions are *not* soft-deleted: they are factual records that tie a user
    to a comment.

Realtime readiness
    All three models carry standard ``created_at`` / ``updated_at`` fields.
    The service layer is designed so that a Channels/WebSocket layer can
    subscribe to ``post_save`` signals and broadcast changes without any
    model changes.
"""

from __future__ import annotations

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

AUTH_USER = settings.AUTH_USER_MODEL


# ---------------------------------------------------------------------------
# Choices
# ---------------------------------------------------------------------------

class AnnotationType(models.TextChoices):
    HIGHLIGHT = "highlight", _("Highlight")
    NOTE = "note", _("Note")
    FREEHAND = "freehand", _("Freehand Drawing")
    STAMP = "stamp", _("Stamp")
    REDACTION = "redaction", _("Redaction")


# ---------------------------------------------------------------------------
# Comment
# ---------------------------------------------------------------------------

class Comment(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """Text discussion note on a Document or a specific DocumentVersion.

    Threading
    ---------
    ``parent`` points to the root-level Comment.  The service layer enforces
    one level of nesting: replies cannot themselves have replies.  This matches
    the UX convention used by most enterprise document tools (e.g. Google Docs,
    Confluence) and keeps the data model and API simple.

    Resolution workflow
    -------------------
    ``is_resolved`` + ``resolved_by`` + ``resolved_at`` allow review workflows
    (e.g. "all comments resolved before final approval").  Resolution does NOT
    delete the comment.
    """

    document = models.ForeignKey(
        "dms.Document",
        on_delete=models.CASCADE,
        related_name="comments",
        verbose_name=_("document"),
    )
    version = models.ForeignKey(
        "dms.DocumentVersion",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="comments",
        verbose_name=_("document version"),
        help_text=_("Leave blank for a document-level comment."),
    )
    parent = models.ForeignKey(
        "self",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="replies",
        verbose_name=_("parent comment"),
    )
    author = models.ForeignKey(
        AUTH_USER,
        on_delete=models.SET_NULL,
        null=True,
        related_name="dms_comments",
        verbose_name=_("author"),
    )
    body = models.TextField(_("body"))

    # Resolution
    is_resolved = models.BooleanField(_("resolved"), default=False)
    resolved_by = models.ForeignKey(
        AUTH_USER,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="dms_resolved_comments",
        verbose_name=_("resolved by"),
    )
    resolved_at = models.DateTimeField(_("resolved at"), null=True, blank=True)

    objects = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("comment")
        verbose_name_plural = _("comments")
        indexes = [
            models.Index(
                fields=["document", "is_deleted"],
                name="dms_comment_doc_idx",
            ),
            models.Index(
                fields=["document", "is_resolved", "is_deleted"],
                name="dms_comment_doc_resolved_idx",
            ),
            models.Index(
                fields=["version", "is_deleted"],
                name="dms_comment_ver_idx",
            ),
            models.Index(
                fields=["parent", "is_deleted"],
                name="dms_comment_parent_idx",
            ),
        ]

    def __str__(self) -> str:
        snippet = self.body[:40]
        return f"Comment({self.pk}, '{snippet}')"


# ---------------------------------------------------------------------------
# Annotation
# ---------------------------------------------------------------------------

class Annotation(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """Position-aware mark-up on a DocumentVersion.

    ``position_data`` is intentionally unschematised at the DB layer.
    Different front-end viewers (PDF.js, PSPDFKit, custom canvas) use
    different coordinate systems.  The DMS stores and returns whatever the
    client sends; validation is the client's responsibility.

    ``document`` is denormalised (always == version.document) to allow
    efficient ``/documents/{id}/annotations/`` queries without a join.
    """

    document = models.ForeignKey(
        "dms.Document",
        on_delete=models.CASCADE,
        related_name="annotations",
        verbose_name=_("document"),
    )
    version = models.ForeignKey(
        "dms.DocumentVersion",
        on_delete=models.CASCADE,
        related_name="annotations",
        verbose_name=_("document version"),
    )
    author = models.ForeignKey(
        AUTH_USER,
        on_delete=models.SET_NULL,
        null=True,
        related_name="dms_annotations",
        verbose_name=_("author"),
    )
    annotation_type = models.CharField(
        _("annotation type"),
        max_length=20,
        choices=AnnotationType.choices,
        default=AnnotationType.NOTE,
    )
    page_number = models.PositiveIntegerField(
        _("page number"),
        null=True,
        blank=True,
        help_text=_("1-based page number for multi-page documents."),
    )
    position_data = models.JSONField(
        _("position data"),
        default=dict,
        help_text=_("Viewer-specific coordinate / bounding-box JSON."),
    )
    body = models.TextField(_("body"), blank=True, default="")
    color = models.CharField(
        _("color"),
        max_length=7,
        default="#FFFF00",
        help_text=_("CSS hex color for the annotation mark."),
    )
    linked_comment = models.OneToOneField(
        Comment,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="annotation",
        verbose_name=_("linked comment"),
    )

    objects = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("annotation")
        verbose_name_plural = _("annotations")
        indexes = [
            models.Index(
                fields=["version", "is_deleted"],
                name="dms_annot_version_idx",
            ),
            models.Index(
                fields=["document", "is_deleted"],
                name="dms_annot_doc_idx",
            ),
            models.Index(
                fields=["version", "page_number", "is_deleted"],
                name="dms_annot_page_idx",
            ),
        ]

    def __str__(self) -> str:
        return f"Annotation({self.pk}, type={self.annotation_type})"


# ---------------------------------------------------------------------------
# Mention
# ---------------------------------------------------------------------------

class Mention(UUIDModel, TenantScopedModel, AuditedModel):
    """Records a user @mention inside a comment body.

    Not soft-deleted — a mention is a factual record tying a user to a
    comment.  Removing a mention requires deleting this row (which the
    service layer does on comment edit if the mention is no longer present).

    ``is_notified`` is flipped by the notification pipeline once the external
    notification engine has dispatched the alert, preventing duplicates.
    """

    comment = models.ForeignKey(
        Comment,
        on_delete=models.CASCADE,
        related_name="mentions",
        verbose_name=_("comment"),
    )
    mentioned_user = models.ForeignKey(
        AUTH_USER,
        on_delete=models.CASCADE,
        related_name="dms_mentions",
        verbose_name=_("mentioned user"),
    )
    is_notified = models.BooleanField(
        _("notified"),
        default=False,
        help_text=_("True once the notification engine has dispatched the alert."),
    )

    class Meta:
        verbose_name = _("mention")
        verbose_name_plural = _("mentions")
        unique_together = [("comment", "mentioned_user")]
        indexes = [
            models.Index(
                fields=["comment"],
                name="dms_mention_comment_idx",
            ),
            models.Index(
                fields=["mentioned_user", "is_notified"],
                name="dms_mention_user_notified_idx",
            ),
        ]

    def __str__(self) -> str:
        return f"Mention(comment={self.comment_id}, user={self.mentioned_user_id})"
