from typing import Any
from typing import cast

from alliance_platform.storage.async_uploads.registry import default_async_field_registry
from alliance_platform.storage.async_uploads.storage.filesystem import FileSystemAsyncUploadStorage
from alliance_platform.storage.async_uploads.views.api import ViewProtocol
from alliance_platform.storage.settings import ap_storage_settings
from django.core.files.uploadedfile import UploadedFile
from django.core.signing import BadSignature
from django.core.signing import SignatureExpired
from django.http import FileResponse
from django.http import HttpRequest
from django.http import HttpResponse
from django.http import HttpResponseBadRequest
from django.http import HttpResponseForbidden
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt


@method_decorator(csrf_exempt, name="dispatch")
class FileSystemAsyncStorageUploadView(View):
    """View to handle a direct upload to the filesystem when using :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage`.

    Each view is tied to a specific registry which you can specify in the ``registry`` kwarg (defaults to :data:`~alliance_platform.storage.async_uploads.registry.default_async_field_registry`).

    Note that this view does works off of signed data generated by :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage.generate_upload_url`,
    and does not itself check any permissions. It does verify the signed data, including enforcing an expiry based on
    :data:`~alliance_platform.storage.settings.AlliancePlatformStorageSettings.UPLOAD_URL_EXPIRY`. This is similar to a
    signed URL generated by the S3 or Azure backends.

    The permission checking is done in :class:`~alliance_platform.storage.async_uploads.views.GenerateUploadUrlView`, which
    calls :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage.generate_upload_url`.

    The URL for this view is automatically registered when :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage`
    is used if you have followed the :ref:`register-urls` guide.
    """

    registry = default_async_field_registry

    registration_attach_key = "file_system_async_upload_storage_upload_view"

    @classmethod
    def as_view(cls, **initkwargs: Any):
        view: ViewProtocol = cast(ViewProtocol, super().as_view(**initkwargs))
        registry = view.view_initkwargs.get("registry", default_async_field_registry)
        if hasattr(registry, cls.registration_attach_key):
            import warnings

            warnings.warn(
                f"{registry} has already been attached to one FileSystemAsyncUploadStorage. Pass a different registry in like `FileSystemAsyncStorageUploadView.as_view({{ registry: another_reg }})`"
            )
        setattr(registry, cls.registration_attach_key, view)
        return view

    def post(self, request: HttpRequest):
        signed_path = request.GET.get("path")
        field_id = request.GET.get("field_id")

        if not signed_path or not field_id:
            return HttpResponseBadRequest("Missing signed path.")
        field = self.registry.fields_by_id.get(field_id)
        if not field:
            return HttpResponseBadRequest("Missing field_id.")
        storage = cast(FileSystemAsyncUploadStorage, field.storage)

        try:
            path = storage.signer.unsign(signed_path, max_age=ap_storage_settings.UPLOAD_URL_EXPIRY)
        except SignatureExpired:
            return HttpResponseForbidden("Upload URL has expired.")
        except BadSignature:
            return HttpResponseForbidden("Invalid upload URL.")

        if "file" not in request.FILES:
            return HttpResponseBadRequest("No file uploaded.")

        # Save the uploaded file to the specified path
        uploaded_file = cast(UploadedFile, request.FILES["file"])
        storage.save(path, uploaded_file)

        return HttpResponse("File uploaded successfully.", status=201)


class FileSystemAsyncStorageDownloadView(View):
    """View to handle a direct download from the filesystem when using :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage`.

    Each view is tied to a specific registry which you can specify in the ``registry`` kwarg (defaults to :data:`~alliance_platform.storage.async_uploads.registry.default_async_field_registry`).

    Note that this view does works off of signed data generated by :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage.generate_download_url`,
    and does not itself check any permissions. It does verify the signed data, including enforcing an expiry based on
    :data:`~alliance_platform.storage.settings.AlliancePlatformStorageSettings.DOWNLOAD_URL_EXPIRY`. This is similar to a
    signed URL generated by the S3 or Azure backends.

    The permission checking is done in :class:`~alliance_platform.storage.async_uploads.views.GenerateUploadUrlView`, which
    calls :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage.generate_upload_url`.

    The URL for this view is automatically registered when :class:`~alliance_platform.storage.async_uploads.storage.filesystem.FileSystemAsyncUploadStorage`
    is used if you have followed the :ref:`register-urls` guide.
    """

    registry = default_async_field_registry

    registration_attach_key = "file_system_async_upload_storage_download_view"

    @classmethod
    def as_view(cls, **initkwargs: Any):
        view: ViewProtocol = cast(ViewProtocol, super().as_view(**initkwargs))
        registry = view.view_initkwargs.get("registry", default_async_field_registry)
        if hasattr(registry, cls.registration_attach_key):
            import warnings

            warnings.warn(
                f"{registry} has already been attached to one FileSystemAsyncDownloadStorage. Pass a different registry in like `FileSystemAsyncStorageDownloadView.as_view({{ registry: another_reg }})`"
            )
        setattr(registry, cls.registration_attach_key, view)
        return view

    def get(self, request: HttpRequest):
        signed_path = request.GET.get("path")
        field_id = request.GET.get("field_id")

        if not signed_path or not field_id:
            return HttpResponseBadRequest("Missing signed path.")
        field = self.registry.fields_by_id.get(field_id)
        if not field:
            return HttpResponseBadRequest("Missing field_id.")
        storage = cast(FileSystemAsyncUploadStorage, field.storage)

        try:
            path = storage.signer.unsign(signed_path, max_age=ap_storage_settings.DOWNLOAD_URL_EXPIRY)
        except SignatureExpired:
            return HttpResponseForbidden("Download URL has expired.")
        except BadSignature:
            return HttpResponseForbidden("Invalid download URL.")

        file_handle = storage.open(path, "rb")
        filename = path.split("/")[-1]
        response = FileResponse(file_handle, as_attachment=True, filename=filename)
        return response
