# from django.shortcuts import render
import re
import json
from .serializers import DataAccessSerializer

from rest_framework import viewsets
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status

from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.db.models import Q

from collections import defaultdict
from urllib.parse import unquote

from .utils import get_serializer, build_serializer_class, get_data, get_serialized_data, HierarchyOrganizer

from .models import ModelLog, BaseModel, SideStoreModel, DataAccess

from .registry import sync_registry, device_registry

class HierarchyApiView(APIView):

    def add_children_recursively(self, instance, updates):
        """
        Recursively add all children of an instance to the updates dictionary.
        """
        child_models = instance.get_children_by_model()

        for child_model_name, children in child_models.items():
            for child in children:
                if isinstance(child, BaseModel):
                    serializer = get_serializer(child, child_model_name)
                    updates[child_model_name][child.id] = serializer.data
                    self.add_children_recursively(child, updates)

    def add_parents_with_hierarchy_organizer(self, instance, hierarchy_organizer, current_level=0):
        """
        Recursively add all parents of an instance to the hierarchy organizer.
        Parents are added at a higher level than their children.
        """
        parent_models = instance.get_parents_by_model()

        for parent_model_name, parent in parent_models.items():
            if isinstance(parent, BaseModel):
                store_id = None
                if isinstance(parent, SideStoreModel):
                    store_id = parent.store_id

                parent_data = {
                    'model_id': parent.id,
                    'store_id': store_id
                }

                # Add parent at the next level
                hierarchy_organizer.add_item(parent_model_name, parent_data, current_level + 1)

                # Recursively process parent's parents
                self.add_parents_with_hierarchy_organizer(parent, hierarchy_organizer, current_level + 1)

    def add_parents_recursively(self, instance, dictionary, minimal=False):
        """
        Recursively add all parents of an instance to the updates dictionary.
        If minimal=True, only add id and store_id.
        """
        parent_models = instance.get_parents_by_model()

        for parent_model_name, parent in parent_models.items():
            if isinstance(parent, BaseModel):
                if minimal:
                    store_id = None
                    if isinstance(parent, SideStoreModel):
                        store_id = parent.store_id
                    dictionary[parent_model_name][parent.id] = {
                        'model_id': parent.id,
                        'store_id': store_id
                    }
                else:
                    serializer = get_serializer(parent, parent_model_name)
                    dictionary[parent_model_name][parent.id] = serializer.data

                self.add_parents_recursively(parent, dictionary, minimal)

class SynchronizationApi(HierarchyApiView):
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):

        device_id = request.data.get('device_id')

        operations = request.data['operations']
        results = []
        # print(f"DataApi post, {len(operations)} operations / device: {device_id}")

        models = set()

        for op in operations:
            result = None
            message = None
            model_operation = op.get('operation')
            model_name = op.get('model_name')
            data = op.get('data')
            data_id = data.get('id')
            device_registry.register(data_id, device_id)
            # print(f'*** 1count = {device_registry.count()}')

            try:
                print(f'{model_operation} : {model_name}, id = {data['id']}')

                models.add(model_name)

                serializer_class = build_serializer_class(model_name)
                data['last_updated_by'] = request.user.id # always refresh the user performing the operation

                # model = apps.get_model(app_label='tournaments', model_name=model_name)
                model = sync_registry.get_model(model_name)

                if model_operation == 'POST':
                    serializer = serializer_class(data=data, context={'request': request})
                    if serializer.is_valid():
                        instance = serializer.save()
                        result = serializer.data
                        response_status = status.HTTP_201_CREATED
                    else:
                        print(f'Data invalid ! {serializer.errors}')
                        message = json.dumps(serializer.errors)
                        response_status = status.HTTP_400_BAD_REQUEST
                elif model_operation == 'PUT':
                    instance = get_data(model_name, data_id)
                    serializer = serializer_class(instance, data=data, context={'request': request})
                    if serializer.is_valid():
                        if instance.last_update <= serializer.validated_data.get('last_update'):
                            serializer.save()
                            result = serializer.data
                            response_status = status.HTTP_200_OK
                        else:
                            result = serializer.data
                            response_status = status.HTTP_203_NON_AUTHORITATIVE_INFORMATION
                    else:
                        print(f'Data invalid ! {serializer.errors}')
                        response_status = status.HTTP_400_BAD_REQUEST
                elif model_operation == 'DELETE':
                    try:
                        instance = get_data(model_name, data_id)
                        try:
                            instance.delete()
                            response_status = status.HTTP_204_NO_CONTENT
                        except model.DoesNotExist: # a relationship was not found, not good
                            response_status = status.HTTP_400_BAD_REQUEST
                    except model.DoesNotExist: # the data was not found, it's ok
                        response_status = status.HTTP_208_ALREADY_REPORTED
                        print(f'{model_name} DoesNotExist, id: {data_id}')

            except Exception as e:
                response_status = status.HTTP_400_BAD_REQUEST
                import traceback
                tb = traceback.extract_tb(e.__traceback__)
                file_name = tb[-1].filename
                line_number = tb[-1].lineno
                print(f'OTHER exception on {model_operation} {model_name} : {str(e)}, type: {type(e)}, id: {data.get('id')}, file: {file_name}, line: {line_number}')
                message = str(e)

            results.append({
                'api_call_id': op.get('api_call_id'),
                'status': response_status,
                'data': result,
                'message': message
            })

        # print(f"sync POST completed for models: {models}")

        return Response({
            'results': results
        }, status=207)  # Multi-Status

    def get(self, request, *args, **kwargs):
        last_update_str = request.query_params.get('last_update')
        device_id = request.query_params.get('device_id')

        print(f'last_update_str = {last_update_str}')
        decoded_last_update = unquote(last_update_str)  # Decodes %2B into +

        # print(f'last_update_str = {last_update_str}')
        # print(f'decoded_last_update = {decoded_last_update}')
        if not decoded_last_update:
            return Response({"error": "last_update parameter is required"}, status=status.HTTP_400_BAD_REQUEST)
        try:
            last_update = timezone.datetime.fromisoformat(decoded_last_update)
        except ValueError:
            return Response({"error": f"Invalid date format for last_update: {decoded_last_update}"}, status=status.HTTP_400_BAD_REQUEST)

        print(f'/data GET: {last_update}')

        logs = self.query_model_logs(last_update, request.user, device_id)
        print(f'>>> log count = {len(logs)}')

        updates = defaultdict(dict)
        deletions = defaultdict(list)
        grants = defaultdict(dict)
        revocations = defaultdict(list)
        revocations_parents_organizer = HierarchyOrganizer()

        # revocated_parents = defaultdict(dict)

        last_log_date = None
        for log in logs:

            # log.retrieved()
            # log.save()

            # print(f'log date = {log.date}')
            last_log_date = log.date
            try:
                if log.operation in ['POST', 'PUT']:
                    data = get_serialized_data(log.model_name, log.model_id)
                    updates[log.model_name][log.model_id] = data
                elif log.operation == 'DELETE':
                    deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id})
                elif log.operation == 'GRANT_ACCESS':

                    model = sync_registry.get_model(log.model_name)
                    instance = model.objects.get(id=log.model_id)
                    serializer = get_serializer(instance, log.model_name)

                    grants[log.model_name][log.model_id] = serializer.data
                    self.add_children_recursively(instance, grants)
                    self.add_parents_recursively(instance, grants)
                elif log.operation == 'REVOKE_ACCESS':
                    print(f'revoke access {log.model_id} - {log.store_id}')
                    revocations[log.model_name].append({
                        'model_id': log.model_id,
                        'store_id': log.store_id
                    })

                    # Get the model instance and add its parents to hierarchy
                    model = sync_registry.get_model(log.model_name)
                    try:
                        instance = model.objects.get(id=log.model_id)
                        self.add_parents_with_hierarchy_organizer(instance, revocations_parents_organizer)
                    except model.DoesNotExist:
                        pass
            except ObjectDoesNotExist:
                pass

        # Convert updates dict to list for each model
        for model_name in updates:
            updates[model_name] = list(updates[model_name].values())

        # Convert deletions set to list for each model
        for model_name in deletions:
            deletions[model_name] = deletions[model_name]

        for model_name in grants:
            grants[model_name] = list(grants[model_name].values())

        # for model_name in revocation_parents:
        #     revocation_parents[model_name] = list(revocation_parents[model_name].values())

        response_data = {
            "updates": dict(updates),
            "deletions": dict(deletions),
            "grants": dict(grants),
            "revocations": dict(revocations),
            "revocation_parents": revocations_parents_organizer.get_organized_data(),
            "date": last_log_date
            }

        print(f'sync GET response. UP = {len(updates)} / DEL = {len(deletions)} / G = {len(grants)} / R = {len(revocations)}')
        # print(f'sync GET response. response = {response_data}')
        return Response(response_data, status=status.HTTP_200_OK)

    def query_model_logs(self, last_update, user, device_id):
        log_query = Q(date__gt=last_update, user=user)
        if device_id:
            log_query &= ~Q(device_id=device_id) # exclude query
        return ModelLog.objects.filter(log_query)

class UserDataAccessApi(HierarchyApiView):
    permission_classes = [IsAuthenticated]

    def get(self, request, *args, **kwargs):

        try:
            # Get all DataAccess objects where the requesting user is in shared_with
            data_access_objects = DataAccess.objects.filter(
                shared_with=request.user
            ).prefetch_related('shared_with')  # Use prefetch_related for better performance

            data_by_model = defaultdict(dict)

            print(f'>>> grants = {len(data_access_objects)}')

            for data_access in data_access_objects:
                try:
                    model = sync_registry.get_model(data_access.model_name)
                    instance = model.objects.get(id=data_access.model_id)

                    # Get the base data
                    serializer = get_serializer(instance, data_access.model_name)
                    data_by_model[data_access.model_name][data_access.model_id] = serializer.data

                    # Add parents & children recursively
                    self.add_children_recursively(instance, data_by_model)
                    self.add_parents_recursively(instance, data_by_model)

                except ObjectDoesNotExist:
                    continue

            response_data = {
                model_name: list(model_data.values())
                for model_name, model_data in data_by_model.items()
            }
            print(f'response_data = {response_data}')
            return Response(response_data, status=status.HTTP_200_OK)

        except Exception as e:
            return Response({
                'status': 'error',
                'message': str(e)
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

class DataAccessViewSet(viewsets.ModelViewSet):
    queryset = DataAccess.objects.all()
    serializer_class = DataAccessSerializer

    def get_queryset(self):
        if self.request.user:
            return self.queryset.filter(Q(related_user=self.request.user) | Q(shared_with__in=[self.request.user]))
        return []
