"""DMS records — query layer.

All DB reads for RetentionPolicy, LegalHold, and ArchiveRecord go through here.
Views and services must not query these models directly.
"""

from __future__ import annotations

import uuid

from django.db.models import QuerySet

from simorgh.apps.dms.common.exceptions import AssetNotFound
from simorgh.apps.dms.records.models import ArchiveRecord, LegalHold, RetentionPolicy


# ---------------------------------------------------------------------------
# RetentionPolicy queries
# ---------------------------------------------------------------------------

def get_retention_policy(tenant_id: int, public_id: str) -> RetentionPolicy:
    """Return a non-deleted RetentionPolicy by public_id within a tenant.

    Raises ``AssetNotFound`` if not found or deleted.
    """
    try:
        uid = uuid.UUID(str(public_id))
    except (ValueError, AttributeError) as exc:
        raise AssetNotFound(f"Invalid retention policy id: {public_id!r}") from exc

    try:
        return RetentionPolicy.objects.get(
            tenant_id=tenant_id, public_id=uid, is_deleted=False
        )
    except RetentionPolicy.DoesNotExist as exc:
        raise AssetNotFound(f"RetentionPolicy {public_id!r} not found.") from exc


def list_retention_policies(tenant_id: int, *, active_only: bool = True) -> QuerySet:
    """Return retention policies for a tenant.

    Parameters
    ----------
    active_only:
        When True (default), only ``is_active=True`` non-deleted policies.
        When False, all non-deleted policies.
    """
    qs = RetentionPolicy.objects.filter(tenant_id=tenant_id, is_deleted=False)
    if active_only:
        qs = qs.filter(is_active=True)
    return qs.order_by("name")


# ---------------------------------------------------------------------------
# LegalHold queries
# ---------------------------------------------------------------------------

def get_legal_hold(tenant_id: int, public_id: str) -> LegalHold:
    """Return a LegalHold by public_id within a tenant.

    Raises ``AssetNotFound`` if not found.
    """
    try:
        uid = uuid.UUID(str(public_id))
    except (ValueError, AttributeError) as exc:
        raise AssetNotFound(f"Invalid legal hold id: {public_id!r}") from exc

    try:
        return LegalHold.objects.select_related(
            "document", "placed_by", "released_by"
        ).get(tenant_id=tenant_id, public_id=uid)
    except LegalHold.DoesNotExist as exc:
        raise AssetNotFound(f"LegalHold {public_id!r} not found.") from exc


def list_legal_holds_for_document(document, *, active_only: bool = False) -> QuerySet:
    """Return legal holds for a document, newest first.

    Parameters
    ----------
    active_only:
        When True, only holds with ``ended_at=None`` (still active).
    """
    qs = (
        LegalHold.objects.filter(document=document)
        .select_related("placed_by", "released_by")
        .order_by("-started_at")
    )
    if active_only:
        qs = qs.filter(ended_at__isnull=True)
    return qs


def has_active_legal_hold(document) -> bool:
    """Return True if the document has at least one active legal hold."""
    return LegalHold.objects.filter(document=document, ended_at__isnull=True).exists()


# ---------------------------------------------------------------------------
# ArchiveRecord queries
# ---------------------------------------------------------------------------

def get_archive_record(tenant_id: int, public_id: str) -> ArchiveRecord:
    """Return an ArchiveRecord by public_id within a tenant.

    Raises ``AssetNotFound`` if not found.
    """
    try:
        uid = uuid.UUID(str(public_id))
    except (ValueError, AttributeError) as exc:
        raise AssetNotFound(f"Invalid archive record id: {public_id!r}") from exc

    try:
        return ArchiveRecord.objects.select_related(
            "document", "version", "retention_policy", "archived_by"
        ).get(tenant_id=tenant_id, public_id=uid)
    except ArchiveRecord.DoesNotExist as exc:
        raise AssetNotFound(f"ArchiveRecord {public_id!r} not found.") from exc


def list_archive_records_for_document(document) -> QuerySet:
    """Return archive records for a document, newest first."""
    return (
        ArchiveRecord.objects.filter(document=document)
        .select_related("version", "retention_policy", "archived_by")
        .order_by("-archived_at")
    )


def list_expiring_archive_records(tenant_id: int, *, before) -> QuerySet:
    """Return non-permanent archive records whose expiry is on or before ``before``.

    Useful for scheduled jobs that process expired records.

    Parameters
    ----------
    before:
        A ``datetime`` instance.  Records with ``expires_at <= before`` are
        returned.
    """
    return (
        ArchiveRecord.objects.filter(
            tenant_id=tenant_id,
            is_permanent=False,
            expires_at__isnull=False,
            expires_at__lte=before,
        )
        .select_related("document", "retention_policy")
        .order_by("expires_at")
    )
