from rest_framework import status, viewsets
from rest_framework.response import Response
from .models import Projects
from products.models import Products
from products.serializers import ProductsCustomSerializer
from products.permissions import (
    IsCopywriter,
)
from .serializers import (
    ProjectsCustomSerializer,
    ProjectsSerializer,
)
from rest_framework.parsers import MultiPartParser, FormParser
from utils.r2.client import R2
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter
import json
from utils.mixins.archive_mixins import ArchiveViewSetMixin
from .filters import ProjectsFilter
import json
from collections import defaultdict
from copy import copy


# Create your views here.
class ProjectsViewset(viewsets.ModelViewSet, ArchiveViewSetMixin):
    queryset = Projects.objects.order_by("-date_created").prefetch_related(
        "project_product_focuses"
    )
    filter_backends = [DjangoFilterBackend, SearchFilter]
    filterset_class = ProjectsFilter
    permission_classes = [IsCopywriter]

    def get_parsers(self):
        if not getattr(
            self, "request", None
        ):  # Just to fix an error when opening swagger docs
            return []  # pragma: no cover

        return [MultiPartParser(), FormParser()]

    def get_serializer_class(self, *args, **kwargs):
        method = self.request.method

        if method in ["POST", "PUT", "PATCH"]:
            return ProjectsCustomSerializer

        return ProjectsSerializer

    def create_or_update(
        self,
        data,
        featured_products_ids,
        images,
        existing_images_path=[],
        instance=None,
    ):
        if instance:
            serializer = self.get_serializer(data=data, instance=instance)
        else:
            serializer = self.get_serializer(data=data)

        serializer.is_valid(raise_exception=True)
        validated_data = serializer.validated_data

        title = validated_data["title"]
        description = validated_data["description"]
        article_copy = validated_data["article_copy"]
        video_title = validated_data["video_title"]
        video_type = validated_data["video_type"]
        video_link = validated_data["video_link"]
        video_description = validated_data["video_description"]
        product_focus = validated_data.pop("product_focus")
        date_created = validated_data["date_created"]
        thumbnail = validated_data.get("thumbnail", None)
        images_link = []
        thumbnail_r2_path = None

        if len(images) > 0:
            for image in images:
                r2 = R2(file=image, main_folder_name="projects")
                r2_path = r2.upload_fileobj(sub_folder_path=f"{title}/")
                images_link.append(r2_path)

        if thumbnail:
            r2 = R2(file=thumbnail, main_folder_name="projects")
            thumbnail_r2_path = r2.upload_fileobj(sub_folder_path=f"{title}/")

        modified_description = json.dumps({"delta": "", "html": description})
        modified_article_copy = json.dumps({"delta": "", "html": article_copy})

        products = Products.objects.filter(id__in=featured_products_ids)

        if instance:
            project: Projects = instance
            new_images_path = []

            """
            If there are new uploaded images and existing images, combine them before saving
            Otherwise, the new uploaded images will be the new images values of the instance
            Also, check if there are removed images by comparing the old images list and
            new images list. If there are, they will be deleted in the R2 before updating the instance.
            """
            if len(images_link) > 0:
                new_images_path.extend(images_link)

            if len(existing_images_path) > 0:
                new_images_path.extend(existing_images_path)
                old_images_path = project.images

                removed_images_path = list(set(old_images_path) - set(new_images_path))

                if removed_images_path:
                    r2 = R2()
                    r2.delete_objects(r2_path_list=removed_images_path)

            project.title = title
            project.description = modified_description
            project.article_copy = modified_article_copy
            project.video_type = video_type
            project.video_title = video_title
            project.video_link = video_link
            project.video_description = video_description
            project.date_created = date_created
            project.images = new_images_path
        else:
            project = Projects(
                title=title,
                images=images_link,
                description=modified_description,
                article_copy=modified_article_copy,
                video_title=video_title,
                video_type=video_type,
                video_link=video_link,
                video_description=video_description,
                date_created=date_created,
            )

        if thumbnail_r2_path:
            project.thumbnail = thumbnail_r2_path

        project._featured_products = products
        project._product_focuses = product_focus
        project.save()

        read_serializer_data = ProjectsSerializer(project, many=False).data
        read_serializer_data["featured_products"] = ProductsCustomSerializer(
            products, many=True
        ).data

        return read_serializer_data

    def reformat_data_to_acceptable_format(self, data):
        mutable_data = copy(data)
        product_focus_dict = defaultdict(dict)

        for key in data:
            if key.startswith("product_focus["):
                # Extract indices and subkeys
                parts = key.replace("product_focus[", "").split("]")
                if len(parts) >= 2:
                    index = int(parts[0])
                    subkey = parts[1].strip("[").strip("]")
                    if subkey:
                        product_focus_dict[index][subkey] = data[key]

        # Convert defaultdict to list of dicts
        product_focus_list = [product_focus_dict[i] for i in sorted(product_focus_dict)]

        mutable_data["product_focus"] = product_focus_list
        formatted_data = {}

        for key, value in mutable_data.items():
            if not key.startswith("product_focus["):
                if key.endswith("[]"):
                    value = mutable_data.getlist(key, [])
                    key = key.replace("[]", "")

                formatted_data[key] = value

        return formatted_data

    def create(self, request, *args, **kwargs):
        """
        Id is not required for CREATE method.
        """
        data = request.data

        formatted_data = self.reformat_data_to_acceptable_format(data=data)

        images = formatted_data.get("images", [])
        product_focus = formatted_data.get("product_focus", None)
        thumbnail = formatted_data.get("thumbnail", None)

        if thumbnail is None:
            return Response(
                {"message": "Project thumbnail is required."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if len(images) == 0:
            return Response(
                {
                    "message": "At least one project image is required in creating a project."
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        if product_focus is None:
            return Response(
                {"message": "Product focus is required in creating a project."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if len(product_focus) > 4:
            return Response(
                {"message": "Projects can only have 4 product focuses."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        for focus in product_focus:
            product_image = focus.get("product_image", None)
            product_name = focus.get("product_name")

            if product_image is None:
                return Response(
                    {
                        "message": f"Product focus '{product_name}' does not have an image."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

        featured_products_ids = formatted_data.get("featured_products", [])
        init_project_title = formatted_data.get("title")
        date_created = formatted_data.get("date_created", None)

        if date_created is None:
            return Response(
                {"message": "Date Created is required."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        existing_project_title = Projects.objects.filter(
            title__iexact=init_project_title
        )

        if existing_project_title.exists():
            return Response(
                {"message": "Project with the same title already exists."},
                status=status.HTTP_400_BAD_REQUEST,
            )

        # Avoid Serializer Issues and during CREATE
        formatted_data.pop("featured_products", None)
        formatted_data.pop("images", None)

        serialized_created_data = self.create_or_update(
            data=formatted_data,
            featured_products_ids=featured_products_ids,
            images=images,
        )

        return Response(serialized_created_data, status=status.HTTP_201_CREATED)

    def update(self, request, *args, **kwargs):
        """
        Project Images and Product Images of Product Focus are not required if they are
        not new file uploads. Current images path will be retained.

        If a product focus object is updated, include the id in the payload.

        Existing Images Path are just the object path in the R2 storage: https://cdn.app.ph/folder/image.jpg
        """
        data = request.data
        instance: Projects = self.get_object()

        formatted_data = self.reformat_data_to_acceptable_format(data=data)

        images = formatted_data.get("images", [])

        existing_images_path = formatted_data.get("existing_images_path", [])
        images = formatted_data.get("images", [])
        featured_products_ids = formatted_data.get("featured_products", [])
        init_project_title = formatted_data["title"]

        existing_project_title = Projects.objects.filter(
            title__iexact=init_project_title
        ).exclude(id=instance.id)

        if existing_project_title.exists():
            return Response(
                {
                    "message": f"Project with title '{init_project_title}' already exists."
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        formatted_data.pop("featured_products", None)
        formatted_data.pop("existing_images_path", None)
        formatted_data.pop("images", None)

        serialized_updated_data = self.create_or_update(
            data=formatted_data,
            featured_products_ids=featured_products_ids,
            images=images,
            instance=instance,
            existing_images_path=existing_images_path,
        )

        return Response(serialized_updated_data, status=status.HTTP_200_OK)
