"""DMS repositories — query layer.

All DB reads for Repository and Folder go through this module.
Views and services must not issue their own ORM queries 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.repositories.models import Folder, Repository


# ---------------------------------------------------------------------------
# Repository queries
# ---------------------------------------------------------------------------

def get_repository(tenant_id: int, public_id: str) -> Repository:
    """Return a live Repository or raise AssetNotFound."""
    try:
        uid = uuid.UUID(str(public_id))
    except (ValueError, AttributeError) as exc:
        raise AssetNotFound(f"Invalid repository id: {public_id!r}") from exc

    try:
        return Repository.objects.get(tenant_id=tenant_id, public_id=uid, is_deleted=False)
    except Repository.DoesNotExist as exc:
        raise AssetNotFound(f"Repository {public_id!r} not found.") from exc


def list_repositories(
    tenant_id: int,
    *,
    workspace_id: int | None = None,
    search: str | None = None,
) -> QuerySet:
    """Return a QuerySet of active repositories for the given tenant."""
    qs = Repository.objects.filter(tenant_id=tenant_id, is_deleted=False).order_by("name")

    if workspace_id is not None:
        qs = qs.filter(workspace_id=workspace_id)
    if search:
        qs = qs.filter(name__icontains=search)

    return qs


# ---------------------------------------------------------------------------
# Folder queries
# ---------------------------------------------------------------------------

def get_folder(repository: Repository, public_id: str) -> Folder:
    """Return a live Folder inside the repository or raise AssetNotFound."""
    try:
        uid = uuid.UUID(str(public_id))
    except (ValueError, AttributeError) as exc:
        raise AssetNotFound(f"Invalid folder id: {public_id!r}") from exc

    try:
        return Folder.objects.get(
            repository=repository,
            public_id=uid,
            is_deleted=False,
        )
    except Folder.DoesNotExist as exc:
        raise AssetNotFound(f"Folder {public_id!r} not found.") from exc


def list_root_folders(repository: Repository) -> QuerySet:
    """Return direct root-level (parent=None) folders of a repository."""
    return Folder.objects.filter(
        repository=repository,
        parent__isnull=True,
        is_deleted=False,
    ).order_by("sort_order", "name")


def list_children(folder: Folder) -> QuerySet:
    """Return direct children of a folder."""
    return Folder.objects.filter(
        repository=folder.repository,
        parent=folder,
        is_deleted=False,
    ).order_by("sort_order", "name")


def list_descendants(folder: Folder) -> QuerySet:
    """Return all (recursive) descendants of a folder using materialized path.

    Uses a single DB LIKE query — no recursion, no CTEs.
    """
    prefix = folder.materialized_path + "/"
    return Folder.objects.filter(
        repository=folder.repository,
        materialized_path__startswith=prefix,
        is_deleted=False,
    ).order_by("depth", "sort_order", "name")


def get_ancestors(folder: Folder) -> list[Folder]:
    """Return the ordered list of ancestor Folder objects (root first).

    Resolves the ancestry chain from ``materialized_path`` in a single
    IN query.  The result is sorted by depth (ascending).
    """
    if not folder.materialized_path:
        return []

    parts = folder.materialized_path.split("/")
    ancestor_uuids = parts[:-1]  # exclude self

    if not ancestor_uuids:
        return []

    ancestor_map = {
        str(f.public_id): f
        for f in Folder.objects.filter(
            repository=folder.repository,
            public_id__in=[uuid.UUID(u) for u in ancestor_uuids],
        )
    }
    # Preserve depth order
    return [ancestor_map[u] for u in ancestor_uuids if u in ancestor_map]
