import uuid
import os
from pathlib import Path

from django.conf import settings
from django.http import Http404
from django.core.files.storage import FileSystemStorage
from django.db.models import Q

from rest_framework.viewsets import ModelViewSet
from rest_framework import permissions, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework.parsers import FileUploadParser

from ichec_django_core.models import FormField
from ichec_django_core.views import (
    SearchableModelViewSet,
    ObjectFileDownloadView,
    ObjectFileUploadView,
    PublicViewOwnerFullOrDjangoModelPermissions,
    OwnerFullOrDjangoModelPermissions,
    get_permission,
)

from ..models import AccessCall, AccessCallFacilityReview
from ..serializers import (
    AccessCallListSerializer,
    AccessCallDetailSerializer,
    AccessCallUpdateSerializer,
    AccessCallPublicDetailSerializer,
    AccessCallReviewerDetailSerializer,
    AccessCallFacilityReviewSerializer,
)

from .core import DEFAULT_DOC_FORMATS


class CoordinatorEditOrDjangoModelPermissions(OwnerFullOrDjangoModelPermissions):
    owner_field = "coordinator"
    actions = {
        "Review template": ["board_member", "coordinator", "chair", "admin"],
        "Facility review template": [
            "facility_member",
            "coordinator",
            "chair",
            "admin",
        ],
        "Test report template": [
            "facility_member",
            "applicant",
            "coordinator",
            "chair",
            "admin",
        ],
        "Decision template": ["coordinator", "chair", "admin"],
    }

    def has_action_permission(self, view):
        for role in view.get_roles():
            if role in self.actions[view.get_view_name()]:
                return True
        return False

    def has_permission(self, request, view):

        if not self.has_authenticated_user(request):
            return False

        # For certain protected actions make sure
        # it is active in the requester's role map
        if request.method == "GET" and view.get_view_name() in self.actions.keys():
            return self.has_action_permission(view)

        # Fall through to allow permission checks to happen at object level
        if self.method_handled_by_object(request.method):
            return True

        return super().has_permission(request, view)


class PublicViewCoordinatorEditOrDjangoModelPermissions(
    PublicViewOwnerFullOrDjangoModelPermissions
):
    owner_field = "coordinator"


class AccessCallViewSet(SearchableModelViewSet):
    model = AccessCall
    queryset = AccessCall.objects.filter(active=True)
    serializer_class = AccessCallListSerializer

    serializers = {
        "retrieve": {
            "public": AccessCallPublicDetailSerializer,
            "board_member": AccessCallReviewerDetailSerializer,
            "chair": AccessCallDetailSerializer,
            "coordinator": AccessCallDetailSerializer,
            "admin": AccessCallDetailSerializer,
        },
        "list": AccessCallListSerializer,
        "create": AccessCallUpdateSerializer,
        "update": AccessCallUpdateSerializer,
        "partial_update": AccessCallUpdateSerializer,
    }
    permission_classes = [
        PublicViewCoordinatorEditOrDjangoModelPermissions,
    ]

    ordering_fields = ("status",)
    ordering: tuple[str, ...] = ("-closing_date",)
    search_fields = ["title"]

    @action(
        detail=True,
        permission_classes=[CoordinatorEditOrDjangoModelPermissions],
    )
    def applications_summary(self, request, pk=None):
        return self.download(request, "applications_summary")

    @action(
        detail=True,
        permission_classes=[CoordinatorEditOrDjangoModelPermissions],
    )
    def review_template(self, request, pk=None):
        return self.download(request, "review_template")

    @action(
        detail=True,
        permission_classes=[CoordinatorEditOrDjangoModelPermissions],
    )
    def decision_template(self, request, pk=None):
        return self.download(request, "decision_template")

    @action(
        detail=True,
        permission_classes=[CoordinatorEditOrDjangoModelPermissions],
    )
    def facility_review_template(self, request, pk=None):
        return self.download(request, "facility_review_template")

    @action(
        detail=True,
        permission_classes=[CoordinatorEditOrDjangoModelPermissions],
    )
    def test_report_template(self, request, pk=None):
        return self.download(request, "test_report_template")

    def get_queryset(self):
        """
        Public users can only see public calls
        Registered users can see public calls or ones they are on the board of
        Members can see all calls
        Users with view permission can see all calls
        """

        queryset = ModelViewSet.get_queryset(self)

        if not self.request.user.is_authenticated:
            queryset = queryset.filter(public=True)
        elif not self.has_model_view_or_list_permission():
            queryset = queryset.filter(
                Q(public=True)
                | Q(board_members__id=self.request.user.id)
                | Q(board_chair__id=self.request.user.id)
                | Q(coordinator__id=self.request.user.id)
            ).distinct()
        return queryset

    def has_model_list_permission(self) -> bool:
        return self.self_has_permission(get_permission(self.model, "list"))

    def has_model_view_or_list_permission(self) -> bool:
        return self.has_model_list_permission() or self.has_model_view_permission()

    def get_roles(self) -> list[str]:

        roles = ["public"]
        if not self.request.user.is_authenticated:
            return roles
        roles.extend(self.get_object().get_roles(self.request.user))

        if self.has_model_view_permission():
            roles.append("admin")
        return roles

    def get_serializer_class(self):

        if self.action == "retrieve":
            active_serializer = AccessCallPublicDetailSerializer
            roles = self.get_roles()
            for role, serializer in self.serializers[self.action].items():
                if role in roles:
                    active_serializer = serializer
            return active_serializer
        return self.serializers.get(self.action, self.serializer_class)


class AccessCallUploadView(ObjectFileUploadView):
    model = AccessCall
    queryset = AccessCall.objects.all()
    extensions = DEFAULT_DOC_FORMATS
    permission_classes = [
        permissions.IsAuthenticated,
        CoordinatorEditOrDjangoModelPermissions,
    ]


class AccessCallReviewTemplateUploadView(AccessCallUploadView):
    file_field = "review_template"


class AccessCallFacilityReviewTemplateUploadView(AccessCallUploadView):
    file_field = "facility_review_template"


class AccessCallDecisionTemplateUploadView(AccessCallUploadView):
    file_field = "decision_template"


class AccessCallTestReportTemplateUploadView(AccessCallUploadView):
    file_field = "test_report_template"


class AccessCallFieldDownloadView(ObjectFileDownloadView):

    model = AccessCall
    permission_classes = [
        permissions.IsAuthenticated,
        CoordinatorEditOrDjangoModelPermissions,
    ]

    def get_file(self):
        if not self.value.template:
            raise Http404
        return self.value.template

    def get_basename(self):
        ext = self.value.template.name.split(".")[-1]
        return f"access_call_{self.value.key}.{ext}"

    def get(self, request, *args, **kwargs):
        self.authenticate(request)
        self.has_permission(request)

        self.object = self.get_object()
        self.value = FormField.objects.get(id=kwargs["value_id"])
        return super(ObjectFileDownloadView, self).get(request, *args, **kwargs)


class AccessCallFieldUploadView(GenericAPIView):

    model = AccessCall
    queryset = AccessCall.objects.all()
    parser_classes = [FileUploadParser]
    permission_classes = [
        permissions.IsAuthenticated,
        CoordinatorEditOrDjangoModelPermissions,
    ]
    extensions = (".odt", ".doc", ".docx", ".pdf")

    def put(self, request, *args, **kwargs):
        value = FormField.objects.get(id=kwargs["value_id"])

        media_root = Path(settings.MEDIA_ROOT)
        instance = self.get_object()
        storage_dir = Path(
            f"{self.model._meta.model_name}/{instance.id}/field_templates"
        )
        os.makedirs(media_root / storage_dir, exist_ok=True)

        filename = request.data["file"].name

        _, ext = os.path.splitext(filename)
        if ext not in self.extensions:
            return Response(
                {"reason": "File extension not supported"},
                status=400,
            )

        file_path = f"{uuid.uuid4()}{ext}"
        written_path = FileSystemStorage(location=media_root / storage_dir).save(
            file_path, request.data["file"]
        )

        value.template.original_file_name = filename
        value.template.name = str(storage_dir / written_path)
        value.save()

        return Response(status=204)


class AccessCallFacilityReviewViewSet(viewsets.ModelViewSet):
    queryset = AccessCallFacilityReview.objects.all().order_by("id")
    serializer_class = AccessCallFacilityReviewSerializer
    permission_classes = [
        permissions.IsAuthenticated,
        permissions.DjangoModelPermissions,
    ]
