from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.exceptions import NotFound
from django.core.mail import send_mail
import json
from django.core.cache import cache
from flashcard.models import Flashcard
from flashcard.serializers import FlashcardSerializer, FlashcardKnowledgeSerializer
from exercise.models import Exercise
from exercise.serializers import ExerciseSerializer
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from datetime import datetime, timedelta
from config.const import AcademicLevel, SubjectStudied
from django.utils import timezone, dateparse
from .config import reviewGlobals
import requests
from utils.stringifiers import markdownToQuilldelta, closeJSON
import os
from mistralai import Mistral
from django.http import StreamingHttpResponse
from rest_framework.renderers import JSONRenderer
from utils.token_management import has_tokens
from config.authentication import JWTAuthenticationWithUserCheck, IsAuthenticatedAndOwner
from .renderers import EventStreamRenderer

class FlashcardListView(APIView):
    authentication_classes = [JWTAuthenticationWithUserCheck]
    permission_classes = [IsAuthenticatedAndOwner]

    def get(self, request):
        flashcards = Flashcard.objects.filter(user=request.user.publicIdentifier)
        serializer = FlashcardSerializer(flashcards, many=True)
        return Response(serializer.data)

    def post(self, request):
        data = request.data.copy()
        data['user'] = str(request.user.publicIdentifier)
        serializer = FlashcardSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class FlashcardDetailView(APIView):
    authentication_classes = [JWTAuthenticationWithUserCheck]
    permission_classes = [IsAuthenticatedAndOwner]

    @staticmethod
    def get_object(pid, request=None):
        try:
            if request:
                return Flashcard.objects.get(publicIdentifier=pid, user=request.user.publicIdentifier)
            else:
                return Flashcard.objects.get(publicIdentifier=pid)
        except Flashcard.DoesNotExist:
            raise NotFound({"error": "Flashcard not found"}) # Erreur 404

    def get(self, request, pid):
        flashcard = self.get_object(pid, request) # c'est une instance de la classe Flashcard
        serializer = FlashcardSerializer(flashcard)
        return Response(serializer.data)

    def put(self, request, pid):
        flashcard = self.get_object(pid) # On récupère la flashcard à modifier
        serializer = FlashcardSerializer(flashcard, data=request.data)
        if serializer.is_valid():
            serializer.save()  # Si tout est valide, on sauvegarde les nouvelles données
            return Response(serializer.data)
        return Response(serializer.errors,
                        status=status.HTTP_400_BAD_REQUEST) # Sinon, on ne sauvegarde pas et on renvoit une erreur
    def delete(self, request, pid):
        flashcard = self.get_object(pid)
        try:
            flashcard.delete()
            
            try:
                Flashcard.objects.get(publicIdentifier=pid)
                # Si on arrive ici, la flashcard existe encore
                return Response({"error": "La suppression a échoué"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            except Flashcard.DoesNotExist:
                return Response(status=status.HTTP_204_NO_CONTENT)
                
        except Exception as e:
            print(f"Erreur lors de la suppression: {str(e)}")
            return Response({"error": "Erreur lors de la suppression"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class ReviewFlashcard(APIView):
    authentication_classes = [JWTAuthenticationWithUserCheck]
    permission_classes = [IsAuthenticatedAndOwner]

    def isOlder(self, date1, date2):
        """
        Compare deux dates et renvoie True si la première date est plus ancienne
        :param date1: Première date à comparer (datetime object)
        :param date2: Deuxième date à comparer (datetime object)
        :return: Boolean indiquant si date1 est plus ancienne que date2
        """
        # On s'assure que les dates sont conscientes du fuseau horaire
        if date1.tzinfo is None:
            date1 = timezone.make_aware(date1)
        if date2.tzinfo is None:
            date2 = timezone.make_aware(date2)
            
        return date1 < date2

    def checkDatePassed(self, date, threshold):
        """
        Vérifie si la date donnée est dépassée depuis plus que le seuil spécifié.
        :param date: La date à vérifier (datetime object)
        :param threshold: Le seuil en minutes (int)
        :return: Boolean indiquant si la date est dépassée depuis plus que le seuil
        """
        # if not isinstance(date, datetime):
        #     raise ValueError("La date doit être un objet datetime")
        if threshold == 0:
            return True # On optimise dans ce cas spécifique puisque la dernière condition renverrait True tout le temps
        # Assurons-nous que la date est consciente du fuseau horaire
        if date.tzinfo is None:
            date = timezone.make_aware(date)
        now = timezone.now()
        return (now - date) < timedelta(minutes=threshold)

    def isMatstered(self, flashcard):
        """
        Renvoie un booléen dépendant de lastMasteryLevel et lastRevisionTime de la flashcard
        """
        masteryLevel = flashcard.lastMasteryLevel
        return self.checkDatePassed(flashcard.lastRevisionTime, reviewGlobals['validationCondition'][masteryLevel])
    
    def rankSort(self, flashcardList):
        if not flashcardList:
            return [] # Retourne une liste vide si la liste d'entrée est vide
        return sorted(flashcardList, key=lambda x: x['weight']) # Trie la liste de dictionnaires en fonction de la clé 'weight'

    def nextFlashcard(self, flashcardList, lastFlashcardPid=None):
        """
        Renvoie la prochaine flashcard à réviser, ou renvoie None si toutes les flashcards sont maitrisées à un niveau suffisant
        @param flashcardList:
        @param lastFlashcardPid:
        @return:
        """
        if lastFlashcardPid:
            sortedList = Flashcard.objects.filter(publicIdentifier__in=flashcardList).exclude(publicIdentifier=lastFlashcardPid).order_by('weight')
            lastFlashcard = FlashcardDetailView.get_object(lastFlashcardPid)
        else:
            sortedList = Flashcard.objects.filter(publicIdentifier__in=flashcardList).order_by('weight')
            lastFlashcard = None
        lowerLevel = reviewGlobals['defaultAcceptableLevelRank'] # On initialise avec la valeur considérée comme minimum acceptable
        flashcardToSend = None
        flashcardsToRevise = []
        for flashcard in sortedList:
            if not self.isMatstered(flashcard):
                print("Is not mastered : ", flashcard.question)
                flashcardsToRevise.append(flashcard)
        
        if len(flashcardsToRevise) == 0:
            for flashcard in sortedList:
                if lowerLevel > reviewGlobals['levelRank'][flashcard.lastMasteryLevel]:
                    print("Is not reviewed : ", flashcard.question)
                    flashcardsToRevise.append(flashcard)
        
        for fls in flashcardsToRevise:
            print(fls.question)
            if flashcardToSend == None or self.isOlder(fls.lastRevisionTime, flashcardToSend.lastRevisionTime):
                flashcardToSend = fls
        
        if flashcardToSend == None:
            if lastFlashcard != None and reviewGlobals['defaultAcceptableLevelRank'] > reviewGlobals['levelRank'][lastFlashcard.lastMasteryLevel]:
                flashcardToSend = lastFlashcard
        
        return flashcardToSend


    def saveWithNewRank(self, flashcardData):
        flashcard = FlashcardDetailView.get_object(flashcardData['publicIdentifier']) # RISQUE DE SECURITE (flashcardData['publicIdentifier'])
        lastMasteryLevel = flashcardData.get('lastMasteryLevel')
        if not lastMasteryLevel:
            raise Response({'error': 'The datas are not valid'}, status=status.HTTP_400_BAD_REQUEST)

        naturalRank = flashcard.weight * flashcard.numberOfTimeViewed
        flashcard.numberOfTimeViewed += 1 # La carte a été révisée une fois de plus
        flashcard.weight = (naturalRank + reviewGlobals['levelRank'][lastMasteryLevel]) / flashcard.numberOfTimeViewed # RISQUE DE SECURITE (flashcardData['lastMasteryLevel'])
        flashcard.lastRevisionTime = timezone.now()
        flashcard.lastMasteryLevel = lastMasteryLevel
        flashcard.save()

    def put(self, request):
        """
        Sauvegarde la flashcard renvoyée avec la nouvelle valeur de lastMasteryLevel et la nouvelle date.
        Renvoie la flashcard suivante
        @param request:
        @return:
        """
        flashcard = request.data.get('flashcard')
        flashcardList = request.data.get('flashcardList')
        if not(flashcard and flashcardList):
            return Response({'error': 'A valid flashcard and flashcardList attribute must be sent in the body of the request'}, status=status.HTTP_400_BAD_REQUEST)
        self.saveWithNewRank(flashcard)
        nextFlashcard = self.nextFlashcard(flashcardList, lastFlashcardPid=flashcard['publicIdentifier'])
        if nextFlashcard == None:
            return Response({'message': 'All flashcards are mastered at max level', 'flashcard': None}, status=status.HTTP_200_OK)
        serializer = FlashcardSerializer(nextFlashcard)
        return Response({'flashcard': serializer.data}, status=status.HTTP_200_OK)

    def post(self, request):
        try:
            flashcardList = request.data.get('flashcardList')
        except:
            return Response({'error': 'A valid flashcardList attribute must be sent in the body of the request'}, status=status.HTTP_400_BAD_REQUEST)

        nextFlashcardData = self.nextFlashcard(flashcardList)
        if nextFlashcardData == None:
            return Response({'message': 'All flashcards are mastered at max level', 'flashcard': None}, status=status.HTTP_200_OK)
        flashcard = FlashcardDetailView.get_object(nextFlashcardData.publicIdentifier)
        serializer = FlashcardSerializer(flashcard)
        return Response({'flashcard': serializer.data}, status=status.HTTP_200_OK)


# class EventStreamRenderer(BaseRenderer):
#     media_type = 'text/event-stream'
#     format = 'event-stream'
    
#     def render(self, data, accepted_media_type=None, renderer_context=None):
#         return data

class KnowledgePrompt(APIView):
    authentication_classes = [JWTAuthenticationWithUserCheck]
    permission_classes = [IsAuthenticatedAndOwner]
    
    renderer_classes = [EventStreamRenderer]

    def generate_streaming_response(self):
        def stream_content():
            try:
                url = "https://api.mistral.ai/v1/agents/completions"
                headers = {
                    'Authorization': f'Bearer H20rSMbOuAzRkkuUTUS64tfTjyzfPDRb',
                    'Content-Type': 'application/json',
                }
                
                body = {
                    "agent_id": "ag:65468023:20241130:neferti:2485f047",
                    "messages": self.messages,
                    "stream": True,
                    "max_tokens": 500,
                }

                response = requests.post(url, headers=headers, json=body, stream=True)
                content_piece = ""
                for line in response.iter_lines():
                    if line:
                        line = line.decode('utf-8')
                        if line.startswith('data: '):
                            if line == 'data: [DONE]':
                                break
                            json_str = line[6:]  # Enlève le préfixe 'data: '
                            chunk = json.loads(json_str)
                            if chunk['choices'][0]['delta'].get('content'):
                                content_piece += chunk['choices'][0]['delta']['content']
                                deltaAdded = closeJSON(markdownToQuilldelta(content_piece))
                                yield json.dumps(deltaAdded) + '\n'

                print(f"Nombre de tokens utilisés: {response.json()['usage']['total_tokens']}")
                add_tokens_used(request.user, response.json()['usage']['total_tokens'])
            except Exception as e:
                print(f"Erreur lors du streaming : {str(e)}")
                yield json.dumps({'error': str(e)}) + '\n'

        return StreamingHttpResponse(
            streaming_content=stream_content(),
            status=status.HTTP_200_OK,
            content_type='text/event-stream'
        )

    def post(self, request):

        if not has_tokens(request.user):
            print("You have no tokens left")
            print(request.user.email)
            return Response({'error': 'You have no tokens left'}, status=status.HTTP_402_PAYMENT_REQUIRED)

        prompt = request.data.get('prompt', None)
        text = request.data.get('text', "Vide")
        images = request.data.get('images', [])

        if len(prompt) + len(text) > 10000:
            return Response({'error': 'Field content is too long'}, status=status.HTTP_400_BAD_REQUEST)
        if not prompt:
            return Response({'error': 'A valid prompt must be sent in the body of the request'}, status=status.HTTP_400_BAD_REQUEST)

        self.messages = [{
            "role": "user",
            "content": f"Question: {prompt}\n Notes: {json.dumps(text)}"
        }]

        for image in images:
            self.messages.append({
                "role": "user",
                "content": [{
                    "type": "image_url",
                    "image_url": {
                        "detail": "high",
                        "url": image
                    }
                }]
            })

        return self.generate_streaming_response()

class FormatFlashcardKnowledge(APIView):
    authentication_classes = [JWTAuthenticationWithUserCheck]
    permission_classes = [IsAuthenticatedAndOwner]

    @staticmethod
    def convert(ops):
        # Convertie les éventuels champs en markdown dans la flashcard en delta de Quill
        newOps = []
        for op in ops:
            if 'insert' in op and isinstance(op['insert'], str):
                result = markdownToQuilldelta(op['insert'])
                if len(result['ops']) > 1:
                    newOps += result['ops']
                else:
                    newOps.append(op)
            else:
                newOps.append(op)
        return newOps

    def put(self, request):
        flsModel = FlashcardDetailView.get_object(request.data.get('publicIdentifier'))
        flsData = request.data
        ops = flsData['knowledge']['ops']
        newOps = self.convert(ops)
        flsData['knowledge']['ops'] = newOps
        serializer = FlashcardSerializer(flsModel, data=flsData)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

