"""DMS preview bounded context — models.

Design
------
Phase 8 introduces the *preview pipeline*: an asynchronous, provider-agnostic
layer that generates renditions (thumbnails, PDF renders, HTML previews, plain-
text extracts) from a ``DocumentVersion``'s file asset.

Key design decisions
--------------------
Provider-agnostic
    The DMS layer owns *state* (pending / processing / ready / failed) and
    *metadata* (format, dimensions, page number).  The actual byte-crunching
    lives in an external converter service.  Integration happens through two
    service calls:
      ``complete_preview()``   — called by the converter when done
      ``fail_preview()``       — called by the converter on error

Async-ready
    ``provider_job_id`` holds the external job reference so the DMS can poll
    or receive webhooks without extra tables.

Multi-format
    ``PreviewFormat`` covers the common rendition types.  New formats can be
    added without a schema change (just a new choice value).

Multi-page
    ``page_number`` (NULL = the full document / first page) allows per-page
    thumbnails for multi-page PDFs, presentations, etc.

Primary thumbnail
    ``is_primary`` marks the canonical thumbnail shown in document listings.
    Only one primary thumbnail should exist per version (enforced by a partial
    unique constraint).

Immutability
    Once a preview reaches READY or FAILED status its fields are treated as
    immutable by the service layer.  Replacement = new PreviewRecord.

Models
------
PreviewRecord
    The single model for this phase.  One row per (version, format, page).
"""

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 PreviewFormat(models.TextChoices):
    """Rendition format produced by the converter pipeline."""
    THUMBNAIL_PNG = "thumbnail_png", _("Thumbnail (PNG)")
    THUMBNAIL_WEBP = "thumbnail_webp", _("Thumbnail (WebP)")
    PDF_RENDER = "pdf_render", _("PDF Render")
    HTML_PREVIEW = "html_preview", _("HTML Preview")
    TEXT_EXTRACT = "text_extract", _("Text Extract")


class PreviewStatus(models.TextChoices):
    """Lifecycle status of a preview rendition."""
    PENDING = "pending", _("Pending")            # queued, not yet sent to converter
    PROCESSING = "processing", _("Processing")   # converter received the job
    READY = "ready", _("Ready")                  # rendition file is available
    FAILED = "failed", _("Failed")               # converter reported an error
    UNAVAILABLE = "unavailable", _("Unavailable")  # format unsupported for this file


# ---------------------------------------------------------------------------
# PreviewRecord
# ---------------------------------------------------------------------------

class PreviewRecord(UUIDModel, TenantScopedModel, AuditedModel, SoftDeleteModel):
    """A single rendition of a :class:`~dms.DocumentVersion`.

    Each row tracks one (version, format, page_number) combination through
    the full converter lifecycle.

    Relationships
    -------------
    ``document_version``
        The source version whose file asset is being rendered.

    ``file_asset``
        The produced rendition file.  NULL while status is PENDING or
        PROCESSING; populated by ``complete_preview()``.

    Converter integration
    ---------------------
    ``provider``
        Opaque string identifying the converter backend (e.g.
        ``"libreoffice_worker"``, ``"imagemagick_worker"``).

    ``provider_job_id``
        External job reference used for async polling / webhook correlation.
        May be empty while status is PENDING.
    """

    document_version = models.ForeignKey(
        "dms.DocumentVersion",
        on_delete=models.CASCADE,
        related_name="previews",
        verbose_name=_("document version"),
    )

    # Rendition descriptor -----------------------------------------------------
    preview_format = models.CharField(
        _("preview format"),
        max_length=30,
        choices=PreviewFormat.choices,
        db_index=True,
    )
    page_number = models.PositiveSmallIntegerField(
        _("page number"),
        null=True,
        blank=True,
        help_text=_(
            "1-based page number for paginated documents.  NULL means the "
            "whole document (summary thumbnail, full PDF render, etc.)."
        ),
    )

    # Dimensions (for image renditions) ----------------------------------------
    width = models.PositiveSmallIntegerField(_("width px"), null=True, blank=True)
    height = models.PositiveSmallIntegerField(_("height px"), null=True, blank=True)

    # Lifecycle ----------------------------------------------------------------
    status = models.CharField(
        _("status"),
        max_length=20,
        choices=PreviewStatus.choices,
        default=PreviewStatus.PENDING,
        db_index=True,
    )
    failure_reason = models.TextField(
        _("failure reason"),
        blank=True,
        default="",
        help_text=_("Human-readable error message when status=FAILED."),
    )

    # Provider info (for async + observability) --------------------------------
    provider = models.CharField(
        _("provider"),
        max_length=100,
        blank=True,
        default="",
        help_text=_("Converter backend identifier, e.g. 'libreoffice_worker'."),
    )
    provider_job_id = models.CharField(
        _("provider job id"),
        max_length=255,
        blank=True,
        default="",
        help_text=_("External job reference for async polling / webhook routing."),
    )

    # Result -------------------------------------------------------------------
    file_asset = models.ForeignKey(
        "storage.FileMetadata",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="preview_records",
        verbose_name=_("result file asset"),
        help_text=_("Populated when status=READY."),
    )

    # Mirrored file fields for cheap listing queries --------------------------
    file_size_bytes = models.PositiveBigIntegerField(
        _("file size (bytes)"), null=True, blank=True
    )
    content_type = models.CharField(
        _("content type"), max_length=200, blank=True, default=""
    )

    # Primary flag ------------------------------------------------------------
    is_primary = models.BooleanField(
        _("primary"),
        default=False,
        help_text=_(
            "Marks the canonical thumbnail shown in document listings.  "
            "Enforced unique per version in service layer."
        ),
    )

    objects: ClassVar[ScopedSoftDeleteManager] = ScopedSoftDeleteManager()

    class Meta:
        verbose_name = _("preview record")
        verbose_name_plural = _("preview records")
        ordering = ("preview_format", "page_number")
        constraints = [
            # Only one primary preview per document version.
            models.UniqueConstraint(
                fields=("document_version",),
                condition=models.Q(is_primary=True, is_deleted=False),
                name="dms_previewrecord_unique_primary_per_version",
            ),
            # Only one record per (version, format, page_number) combination.
            models.UniqueConstraint(
                fields=("document_version", "preview_format", "page_number"),
                condition=models.Q(is_deleted=False),
                name="dms_previewrecord_unique_format_page_per_version",
            ),
        ]
        indexes = [
            models.Index(fields=("document_version", "status", "is_deleted")),
            models.Index(fields=("tenant", "status", "is_deleted")),
            models.Index(fields=("provider_job_id",)),
        ]

    def __str__(self) -> str:
        page = f" p.{self.page_number}" if self.page_number else ""
        return f"PreviewRecord({self.preview_format}{page}, {self.status})"
