"""DMS asset API views.

Endpoints
---------
Assets:
  GET    /dms/assets/                              list file assets (tenant-scoped)
  POST   /dms/assets/                              direct single-request upload
  GET    /dms/assets/{public_id}/                  retrieve asset detail
  DELETE /dms/assets/{public_id}/                  soft-delete asset
  GET    /dms/assets/{public_id}/variants/         list file variants

Upload sessions:
  POST   /dms/assets/sessions/                     initiate upload session
  GET    /dms/assets/sessions/{public_id}/         session status
  POST   /dms/assets/sessions/{public_id}/chunk/   receive a chunk
  POST   /dms/assets/sessions/{public_id}/complete/ assemble → FileMetadata
"""

from __future__ import annotations

from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.dms.assets import repositories, services
from simorgh.apps.dms.assets.permissions import (
    PERM_ASSET_DELETE,
    PERM_ASSET_UPLOAD,
    PERM_ASSET_VIEW,
)
from simorgh.apps.dms.assets.serializers import (
    CreateSessionInputSerializer,
    DirectUploadInputSerializer,
    FileAssetSerializer,
    FileVariantSerializer,
    ReceiveChunkInputSerializer,
    UploadSessionSerializer,
)
from simorgh.apps.dms.common.exceptions import (
    AssetNotFound,
    ChunkError,
    DMSError,
    UploadSessionError,
)
from simorgh.apps.storage.providers import StorageError


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _tenant(request: Request):
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        from rest_framework.exceptions import PermissionDenied
        raise PermissionDenied("Tenant required.")
    return tenant


def _org_node_id(request: Request) -> int:
    """Resolve the request actor's primary org-node id."""
    from simorgh.apps.memberships.models import Membership, MembershipStatus

    m = (
        Membership.objects.filter(
            tenant=_tenant(request),
            users=request.user,
            status=MembershipStatus.ACTIVE,
        )
        .order_by("id")
        .first()
    )
    if m is not None:
        return m.organization_node_id
    if getattr(request.user, "is_superuser", False):
        from simorgh.apps.organizations.models import OrganizationNode

        root = (
            OrganizationNode.objects.filter(tenant=_tenant(request))
            .order_by("id")
            .first()
        )
        if root is not None:
            return root.pk
    from rest_framework.exceptions import PermissionDenied
    raise PermissionDenied("No active membership for this tenant.")


def _require_perm(request: Request, codename: str) -> None:
    """Raise PermissionDenied if the current actor lacks ``codename``."""
    from rest_framework.exceptions import PermissionDenied
    from simorgh.core.context import current_request_context

    ctx = current_request_context()
    if ctx.is_superuser:
        return
    if codename not in (ctx.permissions or set()):
        raise PermissionDenied(f"Permission required: {codename}")


# ---------------------------------------------------------------------------
# Asset list + direct upload
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def asset_list(request: Request) -> Response:
    """
    GET  — list DMS file assets for the current tenant.
    POST — direct single-request upload; returns the new FileAsset.
    """
    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, PERM_ASSET_VIEW)
        qs = repositories.list_assets(
            tenant_id=tenant.pk,
            upload_status=request.query_params.get("upload_status"),
            folder_id=request.query_params.get("folder_id"),
            content_type=request.query_params.get("content_type"),
            search=request.query_params.get("search"),
        )
        return Response(FileAssetSerializer(qs, many=True).data)

    # POST — direct upload
    _require_perm(request, PERM_ASSET_UPLOAD)
    ser = DirectUploadInputSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data

    uploaded_file = d["file"]
    try:
        file_asset = services.upload_asset(
            filename=uploaded_file.name,
            content=uploaded_file,
            content_type=uploaded_file.content_type or "",
            tenant_id=tenant.pk,
            organization_node_id=_org_node_id(request),
            uploaded_by_id=request.user.pk,
            folder_id=str(d["folder_id"]) if d.get("folder_id") else None,
        )
    except StorageError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(FileAssetSerializer(file_asset).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Asset detail + delete
# ---------------------------------------------------------------------------

@api_view(["GET", "DELETE"])
@permission_classes([IsAuthenticated])
def asset_detail(request: Request, public_id: str) -> Response:
    """
    GET    — retrieve a single DMS file asset.
    DELETE — soft-delete a DMS file asset.
    """
    tenant = _tenant(request)

    try:
        asset = repositories.get_asset(tenant_id=tenant.pk, public_id=public_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        _require_perm(request, PERM_ASSET_VIEW)
        return Response(FileAssetSerializer(asset).data)

    # DELETE
    _require_perm(request, PERM_ASSET_DELETE)
    asset.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Asset variants
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def asset_variants(request: Request, public_id: str) -> Response:
    """List all FileVariant records for a DMS asset."""
    tenant = _tenant(request)
    _require_perm(request, PERM_ASSET_VIEW)

    try:
        asset = repositories.get_asset(tenant_id=tenant.pk, public_id=public_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    variants = repositories.list_variants(asset)
    return Response(FileVariantSerializer(variants, many=True).data)


# ---------------------------------------------------------------------------
# Upload sessions — initiate
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def session_create(request: Request) -> Response:
    """Initiate a resumable multi-chunk upload session."""
    tenant = _tenant(request)
    _require_perm(request, PERM_ASSET_UPLOAD)

    ser = CreateSessionInputSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data

    try:
        session = services.initiate_session(
            filename=d["filename"],
            content_type=d.get("content_type", ""),
            total_size_bytes=d["total_size_bytes"],
            total_chunks=d.get("total_chunks", 1),
            expected_checksum_sha256=d.get("expected_checksum_sha256", ""),
            tenant_id=tenant.pk,
            organization_node_id=_org_node_id(request),
            uploaded_by_id=request.user.pk,
            folder_id=str(d["folder_id"]) if d.get("folder_id") else None,
        )
    except StorageError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(UploadSessionSerializer(session).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Upload sessions — status
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def session_detail(request: Request, public_id: str) -> Response:
    """Retrieve the current status of an upload session."""
    tenant = _tenant(request)
    _require_perm(request, PERM_ASSET_UPLOAD)

    try:
        session = repositories.get_upload_session(tenant_id=tenant.pk, public_id=public_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    return Response(UploadSessionSerializer(session).data)


# ---------------------------------------------------------------------------
# Upload sessions — chunk
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def session_receive_chunk(request: Request, public_id: str) -> Response:
    """Receive a single chunk for an upload session.

    Expected multipart fields:
      ``chunk_index`` — 0-based integer index of this chunk.
      ``data``        — the chunk file bytes.
    """
    tenant = _tenant(request)
    _require_perm(request, PERM_ASSET_UPLOAD)

    try:
        session = repositories.get_upload_session(tenant_id=tenant.pk, public_id=public_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    ser = ReceiveChunkInputSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data

    try:
        session = services.receive_chunk(
            session=session,
            chunk_index=d["chunk_index"],
            chunk_data=d["data"],
        )
    except (UploadSessionError, ChunkError, DMSError, StorageError) as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(UploadSessionSerializer(session).data)


# ---------------------------------------------------------------------------
# Upload sessions — complete
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def session_complete(request: Request, public_id: str) -> Response:
    """Assemble all chunks and create the FileMetadata record.

    Returns the completed FileAsset on success.
    """
    tenant = _tenant(request)
    _require_perm(request, PERM_ASSET_UPLOAD)

    try:
        session = repositories.get_upload_session(tenant_id=tenant.pk, public_id=public_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    try:
        file_asset = services.complete_session(session)
    except (UploadSessionError, ChunkError, DMSError, StorageError) as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(FileAssetSerializer(file_asset).data, status=status.HTTP_201_CREATED)
