from calendar import monthrange
from datetime import date, timedelta

from flask import request

from management.management.utils.utils import create_audit_log
from management.models.associazione_pratiche_a_modello import PraticheModello
from management.models.gruppo_driver import GruppoDriver
from management.models.nota_permanente import NotaPermanente
from management.models.pratica import Pratica
from management.repositories import cliente
from management.repositories.cliente import ClienteRepository
from management.serializers.cliente_serializer import ClienteListSerializer, ClienteSerializer
from django.db.models import Q, Max, OuterRef, Subquery 
from .base import *


class ClienteAPIView(GenericViewSet):
    authentication_classes = [TokenAuthentication]
    _repository = ClienteRepository()

    @requires_auth(permission=[os.getenv('GESTORE'), os.getenv('INCARICATO')])
    def get_items(self, request: Request):
        try:
            args: FilterAndSorting = json.loads(request.body, object_hook=objJsonDecode)
            
            clienti = self._repository.filtered(args)
            serializer = ClienteListSerializer(clienti, many=True)
            data = serializer.data

            return Response(data, status=status.HTTP_200_OK)

        except Exception as e:
            print(e)
            return Response({'detail': 'An error occurred.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    @requires_auth(permission=[os.getenv('GESTORE'), os.getenv('INCARICATO')])
    def get_active_clients(self, request: Request):
        try:
            clienti = Cliente.objects.filter(cliente_attivo=True)
            serializer = ClienteSerializer(clienti, many=True)
            data = serializer.data

            return Response(data, status=status.HTTP_200_OK)

        except Exception as e:
            print(e)
            return Response({'detail': 'An error occurred.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        
    @requires_auth(permission=[os.getenv('GESTORE'), os.getenv('INCARICATO')])
    def save_item(self, request: Request):
        try:
            with transaction.atomic():
                dto = request.data.dict() if hasattr(request.data, 'dict') else dict(request.data)       
                if isinstance(dto.get('anno_di_applicazione'), list):
                    dto['anno_di_applicazione'] = json.dumps(dto['anno_di_applicazione'])
                status_result, errors = self._repository.save(dto, user_id=request.user_id)
            
                if not status_result:
                    return Response({'detail': 'An error occurred.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

                return Response(GenericResponseObjectSerializer(GenericResponseObject(status=True)).data)
        except Exception as e:
            handle_exception(e)
    @requires_auth(permission=[os.getenv('GESTORE'), os.getenv('INCARICATO')])
    def generate_master(self, request: Request):
        try:
            with transaction.atomic():
                
                dto = request.data.dict() if hasattr(request.data, 'dict') else dict(request.data)
                anno_rif = int(dto["anno"])
                cliente_id = dto["client"]["id"]
                cliente = Cliente.objects.get(id=cliente_id)
                new_values = {
                    "anno_di_applicazione": {
                        "old": '',
                        "new": dto["anno"]
                    }
                }
                create_audit_log(
                    user=request.user_id,
                    activity_type="CREAZIONE PRATICHE BASE",
                    entity=cliente,
                    entity_name='Cliente',
                    entity_id=cliente_id,
                    old_values=None,
                    new_values=new_values
                )
                master_pratiche = PraticheModello.objects.filter(
                    Q(modello=dto["client"]["modello"]), 
                    Q(valido_dal__year__lte=anno_rif), 
                    Q(valido_al__year__gte=anno_rif) | Q(valido_al__isnull=True)
                ).select_related("pratica")

                # pratiche = {m.pratica_id: m.pratica for m in master_pratiche}

                nuovi_piani = []

                # for master in master_pratiche:
                #     p = master.pratica
                #     import pdb
                #     pdb.set_trace()
                #     nuovi_piani.append(PlanArchivio(
                #         cliente_id=cliente_id,
                #         gestore_id=request.user_id,
                #         pratica=p,
                #         title=p.title,
                #         tipo_pratica=p.tipologia,
                #         commessa=p.commessa,
                #         fascicolo=p.fascicolo,
                #         periodicita=p.periodicita,
                #         anno_di_lavorazione=anno_rif,
                #         anno_di_competenza=anno_rif,
                #         periodo_di_competenza=p.mese_standard,
                #         anno_standard=anno_rif,
                #         settimana_di_lavorazione=p.settimana_standard,
                #         mese_di_lavorazione=p.mese_standard,
                #         tariffa=p.tariffa,
                #         spese=p.spese,
                #         remunerabile=bool(p.remunerabile),
                #         autorizzazione_ore_lavorate=p.autorizzazione_ore_lavorate,
                #         percorso_cartella=p.percorso_cartella,
                #         stato_pratica_id=2,
                #         preso_in_carico=False,
                #         documenti=None
                #     ))

                for master in master_pratiche:

                    p = master.pratica

                    anni = self.ensure_list(p.anno_standard)
                    mesi = self.ensure_list(p.mese_standard)
                    settimane = self.ensure_list(p.settimana_standard)

                    for anno in anni:
                        if anno < anno_rif: continue
                        for mese in mesi:
                            max_weeks = self.get_weeks_in_month(anno, mese)
                            for settimana in settimane:
                                if settimana > max_weeks:
                                    continue

                                nuovi_piani.append(
                                    PlanArchivio(
                                        cliente_id=cliente_id,
                                        gestore_id=request.user_id,
                                        pratica=p,
                                        title=p.title,
                                        tipo_pratica=p.tipologia,
                                        commessa=p.commessa,
                                        fascicolo=p.fascicolo,
                                        anno_di_lavorazione=anno,
                                        anno_di_competenza=anno,
                                        periodo_di_competenza=mese,
                                        anno_standard=anno,
                                        settimana_di_lavorazione=settimana,
                                        mese_di_lavorazione=mese,
                                        tariffa=p.tariffa,
                                        spese=p.spese,
                                        stato_pratica_id=2,
                                        preso_in_carico=False,
                                        documenti=None
                                    )
                                )

                PlanArchivio.objects.bulk_create(nuovi_piani)

                plan_creati = PlanArchivio.objects.filter(
                    cliente_id=cliente_id,
                    anno_di_lavorazione=anno_rif
                )

                nuovi_ids = list(plan_creati.values_list("id", flat=True))
                ids_pratiche = list(plan_creati.values_list("pratica_id", flat=True))

                # ultimi_piani_precedenti = PlanArchivio.objects.filter(
                #     pratica_id__in=ids_pratiche
                # ).exclude(
                #     id__in=nuovi_ids
                # ).values('pratica_id').annotate(
                #     ultimo_plan_id=Max('id')
                # )

                # mappa_pratica_plan = {
                #     x["pratica_id"]: x["ultimo_plan_id"]
                #     for x in ultimi_piani_precedenti
                # }

                # ultimo_plan_ids = list(mappa_pratica_plan.values())

                # gruppi_sorgente = GruppoDriver.objects.filter(
                #     plan_id__in=ultimo_plan_ids
                # )

                # nuovi_gruppi = []

                # for piano in plan_creati:
                #     old_plan_id = mappa_pratica_plan.get(piano.pratica_id)

                #     for g in gruppi_sorgente:
                #         if g.plan_id == old_plan_id:
                #             nuovi_gruppi.append(GruppoDriver(
                #                 title=g.title,
                #                 plan=piano
                #             ))

                # GruppoDriver.objects.bulk_create(nuovi_gruppi)
                latest_subquery = Flusso.objects.filter(
                    pratica_id=OuterRef('pratica_id'),
                    random_code=OuterRef('random_code')
                ).order_by('-data_ora_modifica').values('id')[:1]

                flussi_sorgente = Flusso.objects.filter(
                    id=Subquery(latest_subquery),
                    pratica_id__in=ids_pratiche
                ).select_related('group')

                gruppi_ids = set(f.group_id for f in flussi_sorgente if f.group_id)

                gruppi_sorgente = GruppoDriver.objects.filter(id__in=gruppi_ids)

                # mappa gruppi vecchi
                gruppi_map = {g.id: g for g in gruppi_sorgente}

                nuovi_gruppi = []
                mappa_gruppi = {}  # (old_group_id, new_plan_id) -> new_group

                for piano in plan_creati:
                    for g in gruppi_sorgente:
                        nuovo = GruppoDriver(
                            title=g.title,
                            plan=piano
                        )
                        nuovi_gruppi.append(nuovo)

                GruppoDriver.objects.bulk_create(nuovi_gruppi)

                gruppi_creati = GruppoDriver.objects.filter(plan_id__in=nuovi_ids)

                # mappa_gruppi = {}
                # idx = 0
                # for piano in plan_creati:
                #     old_plan_id = mappa_pratica_plan.get(piano.pratica_id)
                #     old_groups = [g for g in gruppi_sorgente if g.plan_id == old_plan_id]

                #     for g_old in old_groups:
                #         mappa_gruppi[(g_old.id, piano.id)] = gruppi_creati[idx]
                #         idx += 1

                # latest_subquery = Flusso.objects.filter(
                #     pratica_id=OuterRef('pratica_id'),
                #     random_code=OuterRef('random_code')
                # ).order_by('-data_ora_modifica').values('id')[:1]

                # flussi_sorgente = Flusso.objects.filter(
                #     id=Subquery(latest_subquery),
                #     pratica_id__in=ids_pratiche
                # ).select_related('group')

                # mappa_flussi = {}
                # for f in flussi_sorgente:
                #     mappa_flussi.setdefault(f.pratica_id, []).append(f)

                # nuovi_flussi = []
                idx = 0
                for piano in plan_creati:
                    for g in gruppi_sorgente:
                        mappa_gruppi[(g.id, piano.id)] = gruppi_creati[idx]
                        idx += 1

                mappa_flussi = {}
                for f in flussi_sorgente:
                    mappa_flussi.setdefault(f.pratica_id, []).append(f)

                nuovi_flussi = []

                for piano in plan_creati:
                    flussi = mappa_flussi.get(piano.pratica_id, [])

                    for f in flussi:
                        nuovo_gruppo = mappa_gruppi.get((f.group_id, piano.id))

                        nuovi_flussi.append(Flusso(
                            title=f.title,
                            descrizione=f.descrizione,
                            note_attivita=f.note_attivita,
                            group=nuovo_gruppo,
                            codice_attivita=f.codice_attivita,
                            stato=0,
                            pratica=piano.pratica,
                            plan=piano,
                            random_code=f.random_code
                        ))

                Flusso.objects.bulk_create(nuovi_flussi)

                # latest_note_subquery = NotaPermanente.objects.filter(
                #     pratica_id=OuterRef('pratica_id'),
                #     random_code=OuterRef('random_code')
                # ).order_by('-decorrenza', '-id').values('id')[:1]

                # note_sorgente = NotaPermanente.objects.filter(
                #     id=Subquery(latest_note_subquery),
                #     pratica_id__in=ids_pratiche
                # )

                # nuove_note = []

                # for piano in plan_creati:
                #     for n_old in note_sorgente.filter(pratica_id=piano.pratica_id):
                #         nuove_note.append(NotaPermanente(
                #             descrizione_nota=n_old.descrizione_nota,
                #             pratica=piano.pratica,
                #             plan=piano,
                #             decorrenza=n_old.decorrenza,
                #             random_code=n_old.random_code
                #         ))

                # NotaPermanente.objects.bulk_create(nuove_note)
                ultimi_piani_precedenti = PlanArchivio.objects.filter(
                    pratica_id__in=ids_pratiche
                ).exclude(
                    id__in=nuovi_ids
                ).values('pratica_id').annotate(ultimo_plan_id=Max('id'))

                ultimo_plan_ids = [item['ultimo_plan_id'] for item in ultimi_piani_precedenti]

                note_sorgente = NotaPermanente.objects.filter(plan_id__in=ultimo_plan_ids)

                mappa_note = {}
                for n in note_sorgente:
                    mappa_note.setdefault(n.pratica_id, []).append(n)

                nuove_note = []
                for piano in plan_creati:
                    for n in mappa_note.get(piano.pratica_id, []):
                        nuove_note.append(NotaPermanente(
                            descrizione_nota=n.descrizione_nota,
                            pratica=piano.pratica,
                            plan=piano,
                            decorrenza=n.decorrenza,
                        ))

                NotaPermanente.objects.bulk_create(nuove_note)

                return Response(
                    GenericResponseObjectSerializer(
                        GenericResponseObject(status=True)
                    ).data
                )

        except Exception as e:
            handle_exception(e)

    def get_weeks_in_month(self, year, month):
        first_day = date(year, month, 1)
        days_until_monday = (7 - first_day.weekday()) % 7

        first_monday = first_day + timedelta(days=days_until_monday)
        last_day_num = monthrange(year, month)[1]
        last_day = date(year, month, last_day_num)

        if first_monday > last_day:
            return 0
        delta_days = (last_day - first_monday).days

        return (delta_days // 7) + 1
    
    def ensure_list(self, value):
        if value is None:
            return []

        if isinstance(value, list):
            return value

        return [value]

    @requires_auth(permission=[os.getenv('GESTORE'), os.getenv('INCARICATO')])
    def generate_recurring(self, request: Request):
        try:
            with transaction.atomic():
                dto = request.data.dict() if hasattr(request.data, 'dict') else dict(request.data)
                data = dto["data"]
                cliente_id = dto["client"]["id"]
                cliente = Cliente.objects.get(id=cliente_id)
                new_values = {
                    "anno_di_applicazione": {
                        "old": '',
                        "new": data["anno"]
                    },
                    "mese_di_applicazione": {
                        "old": '',
                        "new": data["mese"]
                    },

                }
                create_audit_log(
                    user=request.user_id,
                    activity_type="CREAZIONE PRATICHE RICORRENTI",
                    entity=cliente,
                    entity_name='Cliente',
                    entity_id=cliente_id,
                    old_values=None,
                    new_values=new_values
                )
                data_rif = date(int(data["anno"]), int(data["mese"]), 1)

                master_pratiche = PraticheModello.objects.filter(
                    Q(modello=dto["client"]["modello"]),
                    Q(valido_dal__lte=data_rif),
                    Q(valido_al__gte=data_rif) | Q(valido_al__isnull=True)
                )

                nuovi_piani = []
                # for master in master_pratiche:
                #     pratica = master.pratica

                #     nuovi_piani.append(PlanArchivio(
                #         cliente_id=cliente_id,
                #         gestore_id=request.user_id,
                #         pratica=pratica,
                #         title=pratica.title,
                #         tipo_pratica=pratica.tipologia,
                #         commessa=pratica.commessa,
                #         fascicolo=pratica.fascicolo,
                #         periodicita=pratica.periodicita,
                #         anno_di_lavorazione=data["anno"],
                #         anno_di_competenza=data["anno"],
                #         periodo_di_competenza=data["mese"],
                #         anno_standard=data["anno"],
                #         settimana_di_lavorazione=pratica.settimana_standard,
                #         mese_di_lavorazione=data["mese"],
                #         tariffa=pratica.tariffa,
                #         spese=pratica.spese,
                #         remunerabile=bool(pratica.remunerabile),
                #         autorizzazione_ore_lavorate=pratica.autorizzazione_ore_lavorate,
                #         percorso_cartella=pratica.percorso_cartella,
                #         stato_pratica_id=2,
                #         preso_in_carico=False,
                #         documenti=None
                #     ))

                # PlanArchivio.objects.bulk_create(nuovi_piani)

                for master in master_pratiche:

                    p = master.pratica

                    anni = self.ensure_list(p.anno_standard)
                    mesi = self.ensure_list(p.mese_standard)
                    settimane = self.ensure_list(p.settimana_standard)
                    for anno in anni:
                        if anno < int(data["anno"]): continue
                        for mese in mesi:
                            if anno == int(data["anno"]) and mese < int(data["mese"]): continue
                            max_weeks = self.get_weeks_in_month(anno, mese)
                            for settimana in settimane:
                                if settimana > max_weeks:
                                    continue

                                nuovi_piani.append(
                                    PlanArchivio(
                                        cliente_id=cliente_id,
                                        gestore_id=request.user_id,
                                        pratica=p,
                                        title=p.title,
                                        tipo_pratica=p.tipologia,
                                        commessa=p.commessa,
                                        fascicolo=p.fascicolo,
                                        anno_di_lavorazione=anno,
                                        anno_di_competenza=anno,
                                        periodo_di_competenza=mese,
                                        anno_standard=anno,
                                        settimana_di_lavorazione=settimana,
                                        mese_di_lavorazione=mese,
                                        tariffa=p.tariffa,
                                        spese=p.spese,
                                        stato_pratica_id=2,
                                        preso_in_carico=False,
                                        documenti=None
                                    )
                                )

                                nuovi_piani.append(PlanArchivio(
                                    cliente_id=cliente_id,
                                    gestore_id=request.user_id,
                                    pratica=pratica,
                                    title=pratica.title,
                                    tipo_pratica=pratica.tipologia,
                                    commessa=pratica.commessa,
                                    fascicolo=pratica.fascicolo,
                                    periodicita=pratica.periodicita,
                                    anno_di_lavorazione=data["anno"],
                                    anno_di_competenza=data["anno"],
                                    periodo_di_competenza=data["mese"],
                                    anno_standard=data["anno"],
                                    settimana_di_lavorazione=pratica.settimana_standard,
                                    mese_di_lavorazione=data["mese"],
                                    tariffa=pratica.tariffa,
                                    spese=pratica.spese,
                                    remunerabile=bool(pratica.remunerabile),
                                    autorizzazione_ore_lavorate=pratica.autorizzazione_ore_lavorate,
                                    percorso_cartella=pratica.percorso_cartella,
                                    stato_pratica_id=2,
                                    preso_in_carico=False,
                                    documenti=None
                                ))

                PlanArchivio.objects.bulk_create(nuovi_piani)

                plan_creati = PlanArchivio.objects.filter(
                    cliente_id=cliente_id,
                    anno_di_lavorazione=data["anno"],
                    mese_di_lavorazione=data["mese"]
                )

                nuovi_ids = [p.id for p in plan_creati]
                ids_pratiche = [p.pratica_id for p in plan_creati]

                latest_subquery = Flusso.objects.filter(
                    pratica_id=OuterRef('pratica_id'),
                    random_code=OuterRef('random_code')
                ).order_by('-data_ora_modifica').values('id')[:1]

                flussi_sorgente = Flusso.objects.filter(
                    id=Subquery(latest_subquery),
                    pratica_id__in=ids_pratiche
                ).select_related('group')

                gruppi_ids = set(f.group_id for f in flussi_sorgente if f.group_id)

                gruppi_sorgente = GruppoDriver.objects.filter(id__in=gruppi_ids)

                # mappa gruppi vecchi
                gruppi_map = {g.id: g for g in gruppi_sorgente}

                nuovi_gruppi = []
                mappa_gruppi = {}  # (old_group_id, new_plan_id) -> new_group

                for piano in plan_creati:
                    for g in gruppi_sorgente:
                        nuovo = GruppoDriver(
                            title=g.title,
                            plan=piano
                        )
                        nuovi_gruppi.append(nuovo)

                GruppoDriver.objects.bulk_create(nuovi_gruppi)

                gruppi_creati = GruppoDriver.objects.filter(plan_id__in=nuovi_ids)

                # mapping sicuro usando ordine
                idx = 0
                for piano in plan_creati:
                    for g in gruppi_sorgente:
                        mappa_gruppi[(g.id, piano.id)] = gruppi_creati[idx]
                        idx += 1

                mappa_flussi = {}
                for f in flussi_sorgente:
                    mappa_flussi.setdefault(f.pratica_id, []).append(f)

                nuovi_flussi = []

                for piano in plan_creati:
                    flussi = mappa_flussi.get(piano.pratica_id, [])

                    for f in flussi:
                        nuovo_gruppo = mappa_gruppi.get((f.group_id, piano.id))

                        nuovi_flussi.append(Flusso(
                            title=f.title,
                            descrizione=f.descrizione,
                            note_attivita=f.note_attivita,
                            group=nuovo_gruppo,
                            codice_attivita=f.codice_attivita,
                            stato=0,
                            pratica=piano.pratica,
                            plan=piano,
                            random_code=f.random_code
                        ))

                Flusso.objects.bulk_create(nuovi_flussi)

                ultimi_piani_precedenti = PlanArchivio.objects.filter(
                    pratica_id__in=ids_pratiche
                ).exclude(
                    id__in=nuovi_ids
                ).values('pratica_id').annotate(ultimo_plan_id=Max('id'))

                ultimo_plan_ids = [item['ultimo_plan_id'] for item in ultimi_piani_precedenti]

                note_sorgente = NotaPermanente.objects.filter(plan_id__in=ultimo_plan_ids)

                mappa_note = {}
                for n in note_sorgente:
                    mappa_note.setdefault(n.pratica_id, []).append(n)

                nuove_note = []
                for piano in plan_creati:
                    for n in mappa_note.get(piano.pratica_id, []):
                        nuove_note.append(NotaPermanente(
                            descrizione_nota=n.descrizione_nota,
                            pratica=piano.pratica,
                            plan=piano,
                            decorrenza=n.decorrenza,
                        ))

                NotaPermanente.objects.bulk_create(nuove_note)                                                                         
                return Response(GenericResponseObjectSerializer(GenericResponseObject(status=True)).data)
        except Exception as e:
            print(e)
            return Response({'detail': 'An error occurred.'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)