"""DMS documents — API views.

Endpoints
---------
Document types:
  GET    /dms/document-types/                      list
  POST   /dms/document-types/                      create
  GET    /dms/document-types/{type_id}/            retrieve
  PATCH  /dms/document-types/{type_id}/            update
  DELETE /dms/document-types/{type_id}/            soft-delete

Documents:
  GET    /dms/documents/                           list (filterable)
  POST   /dms/documents/                           create shell document
  GET    /dms/documents/{doc_id}/                  retrieve
  PATCH  /dms/documents/{doc_id}/                  update
  DELETE /dms/documents/{doc_id}/                  soft-delete
  POST   /dms/documents/{doc_id}/archive/          archive document

Versions:
  GET    /dms/documents/{doc_id}/versions/         list versions
  POST   /dms/documents/{doc_id}/versions/         add new version
  GET    /dms/documents/{doc_id}/versions/{vid}/   retrieve version
  POST   /dms/documents/{doc_id}/versions/{vid}/publish/   publish version
  POST   /dms/documents/{doc_id}/versions/{vid}/rollback/  rollback (create new)
"""

from __future__ import annotations

import mimetypes

from django.http import StreamingHttpResponse
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.common.exceptions import AssetNotFound
from simorgh.apps.dms.documents import queries, services
from simorgh.apps.dms.documents.permissions import (
    PERM_DOCTYPE_MANAGE,
    PERM_DOCTYPE_VIEW,
    PERM_DOCUMENT_CREATE,
    PERM_DOCUMENT_MANAGE,
    PERM_DOCUMENT_PUBLISH,
    PERM_DOCUMENT_VIEW,
)
from simorgh.apps.dms.documents.serializers import (
    DocumentCreateInputSerializer,
    DocumentSerializer,
    DocumentTypeCreateInputSerializer,
    DocumentTypeSerializer,
    DocumentTypeUpdateInputSerializer,
    DocumentUpdateInputSerializer,
    DocumentVersionSerializer,
    RollbackInputSerializer,
    VersionCreateInputSerializer,
)
from simorgh.apps.dms.documents.services import DocumentError, DocumentTypeError, VersionError


# ---------------------------------------------------------------------------
# Shared helpers (same pattern as repositories/views.py)
# ---------------------------------------------------------------------------

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:
    from simorgh.apps.memberships.models import Membership, MembershipStatus

    t = _tenant(request)
    m = (
        Membership.objects.filter(
            tenant=t,
            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=t).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:
    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}")


# ---------------------------------------------------------------------------
# Document type list / create
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def document_type_list(request: Request) -> Response:
    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, PERM_DOCTYPE_VIEW)
        qs = queries.list_document_types(
            tenant_id=tenant.pk,
            is_active=None,
            search=request.query_params.get("search"),
        )
        return Response(DocumentTypeSerializer(qs, many=True).data)

    # POST
    _require_perm(request, PERM_DOCTYPE_MANAGE)
    ser = DocumentTypeCreateInputSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data
    try:
        doc_type = services.create_document_type(
            name=d["name"],
            code=d["code"],
            tenant_id=tenant.pk,
            organization_node_id=_org_node_id(request),
            description=d.get("description", ""),
            icon=d.get("icon", ""),
            color=d.get("color", ""),
            is_active=d.get("is_active", True),
        )
    except DocumentTypeError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
    return Response(DocumentTypeSerializer(doc_type).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Document type detail / update / delete
# ---------------------------------------------------------------------------

@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def document_type_detail(request: Request, type_id: str) -> Response:
    tenant = _tenant(request)
    try:
        doc_type = queries.get_document_type(tenant.pk, type_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        _require_perm(request, PERM_DOCTYPE_VIEW)
        return Response(DocumentTypeSerializer(doc_type).data)

    if request.method == "PATCH":
        _require_perm(request, PERM_DOCTYPE_MANAGE)
        ser = DocumentTypeUpdateInputSerializer(data=request.data, partial=True)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data
        services.update_document_type(
            doc_type,
            name=d.get("name"),
            description=d.get("description"),
            icon=d.get("icon"),
            color=d.get("color"),
            is_active=d.get("is_active"),
        )
        return Response(DocumentTypeSerializer(doc_type).data)

    # DELETE
    _require_perm(request, PERM_DOCTYPE_MANAGE)
    services.delete_document_type(doc_type)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Document list / create
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def document_list(request: Request) -> Response:
    tenant = _tenant(request)

    if request.method == "GET":
        _require_perm(request, PERM_DOCUMENT_VIEW)

        repo_id = request.query_params.get("repository_id")
        folder_id = request.query_params.get("folder_id")
        doc_type_id = request.query_params.get("document_type_id")

        # Resolve UUID filter params to PKs
        def _resolve_repo(uid_str):
            if not uid_str:
                return None
            from simorgh.apps.dms.repositories.queries import get_repository
            try:
                return get_repository(tenant.pk, uid_str).pk
            except AssetNotFound:
                return None

        def _resolve_folder(uid_str):
            if not uid_str:
                return None
            try:
                import uuid
                from simorgh.apps.dms.documents.models import Document
                from simorgh.apps.dms.repositories.models import Folder
                return Folder.objects.get(
                    public_id=uuid.UUID(uid_str), tenant_id=tenant.pk, is_deleted=False
                ).pk
            except Exception:
                return None

        def _resolve_doctype(uid_str):
            if not uid_str:
                return None
            try:
                return queries.get_document_type(tenant.pk, uid_str).pk
            except AssetNotFound:
                return None

        qs = queries.list_documents(
            tenant_id=tenant.pk,
            repository_id=_resolve_repo(repo_id),
            folder_id=_resolve_folder(folder_id),
            document_type_id=_resolve_doctype(doc_type_id),
            status=request.query_params.get("status"),
            search=request.query_params.get("search") or request.query_params.get("q"),
        )

        # Pagination
        try:
            page = max(1, int(request.query_params.get("page", 1)))
        except (TypeError, ValueError):
            page = 1
        try:
            page_size = min(200, max(1, int(request.query_params.get("page_size", 25))))
        except (TypeError, ValueError):
            page_size = 25

        total = qs.count()
        offset = (page - 1) * page_size
        page_qs = qs[offset: offset + page_size]

        return Response({
            "total": total,
            "page": page,
            "page_size": page_size,
            "results": DocumentSerializer(page_qs, many=True).data,
        })

    # POST
    _require_perm(request, PERM_DOCUMENT_CREATE)
    ser = DocumentCreateInputSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data

    # Resolve repository
    from simorgh.apps.dms.repositories.queries import get_repository
    from simorgh.apps.dms.repositories.models import Folder

    try:
        repo = get_repository(tenant.pk, str(d["repository_id"]))
    except AssetNotFound:
        return Response({"detail": "Repository not found."}, status=status.HTTP_400_BAD_REQUEST)

    # Resolve optional folder
    folder = None
    if d.get("folder_id"):
        try:
            import uuid
            folder = Folder.objects.get(
                public_id=uuid.UUID(str(d["folder_id"])),
                repository=repo,
                is_deleted=False,
            )
        except Folder.DoesNotExist:
            return Response({"detail": "Folder not found."}, status=status.HTTP_400_BAD_REQUEST)

    # Resolve optional document type
    doc_type = None
    if d.get("document_type_id"):
        try:
            doc_type = queries.get_document_type(tenant.pk, str(d["document_type_id"]))
        except AssetNotFound:
            return Response({"detail": "Document type not found."}, status=status.HTTP_400_BAD_REQUEST)

    try:
        doc = services.create_document(
            title=d["title"],
            repository=repo,
            folder=folder,
            document_type=doc_type,
            code=d.get("code", ""),
            workflow_status=d.get("workflow_status", ""),
            tenant_id=tenant.pk,
            organization_node_id=_org_node_id(request),
        )
    except DocumentError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(DocumentSerializer(doc).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Document detail / update / delete
# ---------------------------------------------------------------------------

@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def document_detail(request: Request, doc_id: str) -> Response:
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        _require_perm(request, PERM_DOCUMENT_VIEW)
        from django.core.cache import cache
        from simorgh.apps.dms.cache import document_detail_key, DOCUMENT_DETAIL_TTL

        cache_key = document_detail_key(tenant.pk, doc_id)
        cached = cache.get(cache_key)
        if cached is not None:
            return Response(cached)
        data = DocumentSerializer(doc).data
        cache.set(cache_key, data, timeout=DOCUMENT_DETAIL_TTL)
        return Response(data)

    if request.method == "PATCH":
        _require_perm(request, PERM_DOCUMENT_MANAGE)
        ser = DocumentUpdateInputSerializer(data=request.data, partial=True)
        ser.is_valid(raise_exception=True)
        d = ser.validated_data

        folder_sentinel = ...
        if "folder_id" in d:
            if d["folder_id"] is None:
                folder_sentinel = None
            else:
                from simorgh.apps.dms.repositories.models import Folder
                import uuid
                try:
                    folder_sentinel = Folder.objects.get(
                        public_id=uuid.UUID(str(d["folder_id"])),
                        repository=doc.repository,
                        is_deleted=False,
                    )
                except Folder.DoesNotExist:
                    return Response({"detail": "Folder not found."}, status=status.HTTP_400_BAD_REQUEST)

        doc_type_sentinel = ...
        if "document_type_id" in d:
            if d["document_type_id"] is None:
                doc_type_sentinel = None
            else:
                try:
                    doc_type_sentinel = queries.get_document_type(tenant.pk, str(d["document_type_id"]))
                except AssetNotFound:
                    return Response({"detail": "Document type not found."}, status=status.HTTP_400_BAD_REQUEST)

        try:
            services.update_document(
                doc,
                title=d.get("title"),
                folder=folder_sentinel,
                document_type=doc_type_sentinel,
                workflow_status=d.get("workflow_status"),
                extra=d.get("extra"),
            )
        except DocumentError as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        from simorgh.apps.dms.cache import invalidate_document
        invalidate_document(tenant.pk, doc_id)
        return Response(DocumentSerializer(doc).data)

    # DELETE
    _require_perm(request, PERM_DOCUMENT_MANAGE)
    services.delete_document(doc)
    from simorgh.apps.dms.cache import invalidate_document
    invalidate_document(tenant.pk, doc_id)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Archive document
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def document_archive(request: Request, doc_id: str) -> Response:
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    _require_perm(request, PERM_DOCUMENT_MANAGE)
    services.archive_document(doc)
    return Response(DocumentSerializer(doc).data)


# ---------------------------------------------------------------------------
# Version list / create
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def version_list(request: Request, doc_id: str) -> Response:
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    if request.method == "GET":
        _require_perm(request, PERM_DOCUMENT_VIEW)
        qs = queries.list_versions(doc)
        return Response(DocumentVersionSerializer(qs, many=True).data)

    # POST — add new version
    _require_perm(request, PERM_DOCUMENT_CREATE)
    ser = VersionCreateInputSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    d = ser.validated_data

    file_asset = None
    if d.get("file_asset_id"):
        from simorgh.apps.storage.models import FileMetadata
        try:
            import uuid
            file_asset = FileMetadata.objects.get(
                public_id=uuid.UUID(str(d["file_asset_id"])),
                tenant_id=tenant.pk,
                is_deleted=False,
            )
        except FileMetadata.DoesNotExist:
            return Response({"detail": "File asset not found."}, status=status.HTTP_400_BAD_REQUEST)

    try:
        version = services.add_version(
            document=doc,
            file_asset=file_asset,
            bump=d.get("bump", "minor"),
            label=d.get("label", ""),
            change_summary=d.get("change_summary", ""),
            tenant_id=tenant.pk,
            organization_node_id=_org_node_id(request),
        )
    except (DocumentError, VersionError) as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(DocumentVersionSerializer(version).data, status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Version detail
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def version_detail(request: Request, doc_id: str, version_id: str) -> Response:
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    try:
        version = queries.get_version(doc, version_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    _require_perm(request, PERM_DOCUMENT_VIEW)
    return Response(DocumentVersionSerializer(version).data)


# ---------------------------------------------------------------------------
# Version download URL
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def version_download(request: Request, doc_id: str, version_id: str) -> Response:
    """GET /dms/documents/{doc_id}/versions/{version_id}/download/

    Returns a pre-signed (or media) URL for downloading the file attached to
    the version, plus its filename and content_type.
    """
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    try:
        version = queries.get_version(doc, version_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    _require_perm(request, PERM_DOCUMENT_VIEW)

    if version.file_asset_id is None:
        return Response({"detail": "This version has no attached file."}, status=status.HTTP_404_NOT_FOUND)

    from simorgh.apps.storage.models import FileMetadata
    from simorgh.apps.storage.providers import get_default_provider

    try:
        asset = FileMetadata.objects.get(pk=version.file_asset_id)
    except FileMetadata.DoesNotExist:
        return Response({"detail": "File asset not found."}, status=status.HTTP_404_NOT_FOUND)

    provider = get_default_provider()
    try:
        url = provider.url(asset.path)
    except Exception:
        return Response(
            {"detail": "Could not generate download URL."},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR,
        )

    return Response({
        "url": url,
        "filename": asset.filename,
        "content_type": asset.content_type,
        "size_bytes": asset.size_bytes,
    })


# ---------------------------------------------------------------------------
# Version serve (stream file content directly — avoids storage/media mismatch)
# ---------------------------------------------------------------------------

@api_view(["GET"])
@permission_classes([IsAuthenticated])
def version_serve(request: Request, doc_id: str, version_id: str) -> StreamingHttpResponse:
    """GET /dms/documents/{doc_id}/versions/{version_id}/serve/

    Streams the raw file bytes through Django so the browser never needs
    direct access to the storage root.  Supports ?download=1 to force a
    Content-Disposition: attachment header.
    """
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    try:
        version = queries.get_version(doc, version_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    _require_perm(request, PERM_DOCUMENT_VIEW)

    if version.file_asset_id is None:
        return Response({"detail": "This version has no attached file."}, status=status.HTTP_404_NOT_FOUND)

    from simorgh.apps.storage.models import FileMetadata
    from simorgh.apps.storage.providers import get_default_provider, StorageError

    try:
        asset = FileMetadata.objects.get(pk=version.file_asset_id)
    except FileMetadata.DoesNotExist:
        return Response({"detail": "File asset not found."}, status=status.HTTP_404_NOT_FOUND)

    provider = get_default_provider()
    try:
        fh = provider.open(asset.path)
    except StorageError:
        return Response({"detail": "File not found in storage."}, status=status.HTTP_404_NOT_FOUND)

    content_type = asset.content_type or mimetypes.guess_type(asset.filename)[0] or "application/octet-stream"
    force_download = request.query_params.get("download") == "1"

    def _stream(file_handle, chunk_size=64 * 1024):
        try:
            while True:
                chunk = file_handle.read(chunk_size)
                if not chunk:
                    break
                yield chunk
        finally:
            file_handle.close()

    resp = StreamingHttpResponse(_stream(fh), content_type=content_type)
    resp["Content-Length"] = asset.size_bytes
    disposition = "attachment" if force_download else "inline"
    safe_filename = asset.filename.encode("ascii", errors="replace").decode()
    resp["Content-Disposition"] = f'{disposition}; filename="{safe_filename}"'
    resp["Cache-Control"] = "private, no-store"
    return resp


# ---------------------------------------------------------------------------
# Publish version
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def version_publish(request: Request, doc_id: str, version_id: str) -> Response:
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    try:
        version = queries.get_version(doc, version_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    _require_perm(request, PERM_DOCUMENT_PUBLISH)
    try:
        version = services.publish_version(version)
    except VersionError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(DocumentVersionSerializer(version).data)


# ---------------------------------------------------------------------------
# Rollback to version
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def version_rollback(request: Request, doc_id: str, version_id: str) -> Response:
    tenant = _tenant(request)
    try:
        doc = queries.get_document(tenant.pk, doc_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    try:
        target_version = queries.get_version(doc, version_id)
    except AssetNotFound as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)

    _require_perm(request, PERM_DOCUMENT_CREATE)
    ser = RollbackInputSerializer(data=request.data)
    ser.is_valid(raise_exception=True)

    try:
        new_version = services.rollback_to_version(
            document=doc,
            target_version=target_version,
            change_summary=ser.validated_data.get("change_summary", ""),
            tenant_id=tenant.pk,
            organization_node_id=_org_node_id(request),
        )
    except (DocumentError, VersionError) as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(DocumentVersionSerializer(new_version).data, status=status.HTTP_201_CREATED)
