import uuid
from django.core.exceptions import ValidationError
from arches.app.functions.base import BaseFunction
from arches.app.models.system_settings import settings
from arches.app.models import models
from arches.app.models.tile import Tile
from django.db import connection, transaction
import re
import json
import logging

details = {
    "name": "Datos iniciales",
    "type": "node",
    "description": "Establece los datos iniciales de las instancias de recursos",
    "defaultconfig": {
        "lugar": {"ndeId": "", "ndegrpId": ""},
        "lugar_amplio": {"ndeId": "", "ndegrpId": ""},
        "alcance": {"ndeId": "", "ndegrpId": ""},
        "eta": {"ndeId": "", "ndegrpId": ""},
        "codigo": {"ndeId": "", "ndegrpId": ""},
        "nombre": {"ndeId": "", "ndegrpId": ""},
        "meta_ubicacion": {"ndeId": "", "ndegrpId": ""},
        "meta_identificacion": {"ndeId": "", "ndegrpId": ""},
        "meta_marco_legal": {"ndeId": "", "ndegrpId": ""},
        "meta_caracteristicas": {"ndeId": "", "ndegrpId": ""},
        "valor_ndeId": "",
        "valor_ponderado_ndeId": "",
        "valor_ajuste_ndeId": "",
        "valor_rango_ndeId": "",
        "triggering_nodegroups": [],
    },
    "classname": "DatosInicio",
    "component": "views/components/functions/datos-iniciales",
    "functionid": "6ec500db-51cc-4cc8-a796-ee46a56f6998",
}

class DatosInicio(BaseFunction):

    def get(self):
        raise NotImplementedError

    def componer_codigo(self, tile, request, is_function_save_method=True):
        """
        Función que construye el código de identificación de ficha, en base a
        la ubicación, ámbito y una secuencia numérica
        
        Args:
            self: Objeto código.
            tile: Tile al que se adjuntará/modificará el código.
            request: Solicitud WSGI utilizada para verificar que la llamada sea resultado de una acción del usuario. 
                    Nota: La función devuelve el valor si está vacío.
            is_function_save_method: Un valor booleano que indica si la función que lo llama es la función de guardado.

            jbti - 20.9.25
        """
        #Comprueba que sea un llamado válido
        if request is None and is_function_save_method == True:
            return

        def actualizar_descriptors(str_ficha_id):
            try:
                qry = f"""
                    UPDATE resource_instances
                    SET descriptors = jsonb_set(
                    descriptors,
                    '{{es,map_popup}}', '"Indefinido"')
                    WHERE resourceinstanceid = '{str_ficha_id}'
                    AND descriptors->'es'->>'map_popup' LIKE '<%>'
                    RETURNING descriptors->'es'->>'map_popup';
                """
                with connection.cursor() as cursor:
                    cursor.execute(qry)
                    nvo_descriptor = cursor.fetchone()
                if nvo_descriptor is not None:
                    return nvo_descriptor
                else:
                    return ('Sin actualizar',)

            except Exception as e:
                self.logger.error(e,"\t*** Falla al actualizar descriptor")

        def extraer_depto(str_val_depto): # retorna el departamento como str_concept_value
            try:
                div_admin = '4f6caa05-23e3-42a4-8322-98b914fb3ebf' # (División administrativa)
                region = '2eb6660c-1754-42cf-959d-0edaf7779dae' # Region de Bolivia
                qry = f"""
                    SELECT val.valueid::text, val.value 
                    FROM values val 
                    JOIN values vval
                        ON val.conceptid = vval.conceptid
                    JOIN relations rel
                        ON val.conceptid = rel.conceptidto
                    WHERE val.valuetype = 'prefLabel'
                        AND vval.valuetype = 'hiddenLabel'
                        AND (
                            rel.conceptidfrom = '{div_admin}'
                            OR rel.conceptidfrom = '{region}')  
                        AND vval.value = '{str_val_depto}';
                """
                with connection.cursor() as cursor:
                    cursor.execute(qry)
                    departamento = cursor.fetchone()
                if departamento is not None:
                    return departamento
                else:
                    return ()
            except Exception as e:
                self.logger.error(e,"\t*** Falla al extraer departamento")

        def establecer_grupos(string_id_recurso, int_id_grp_eta, sufijo_eta_model, esRecurso = True):
            int_id_todas_las_etas = 10000
            int_id_eta_model = int_id_grp_eta + sufijo_eta_model
            try:
                qry = f"""
                    INSERT INTO guardian_groupobjectpermission (object_pk, content_type_id, group_id, permission_id)
                    VALUES
                    ('{string_id_recurso}',33,2,130),
                    ('{string_id_recurso}',33,2,131),
                    ('{string_id_recurso}',33,{int_id_todas_las_etas},132),
                    ('{string_id_recurso}',33,{int_id_grp_eta},132),
                    ('{string_id_recurso}',33,{int_id_eta_model},132)
                    ON CONFLICT (group_id, permission_id, object_pk) DO NOTHING;
                """
                with connection.cursor() as cursor:
                    cursor.execute(qry)

            except Exception as e:
                self.logger.error(e,"\t*** Falla al establecer grupos")

        def es_ambito(string_id_concepto_value):
            qry = f"""
                SELECT CASE WHEN EXISTS (
                    SELECT * 
                    FROM relations r RIGHT JOIN values v ON r.conceptidfrom = v.conceptid
                    WHERE r.relationtype = 'narrower' AND v.valueid = '{string_id_concepto_value}'
                )
                THEN CAST(1 AS BIT)
                ELSE CAST(0 AS BIT) END;
            """
            with connection.cursor() as cursor:
                cursor.execute(qry)
                esAmbito = cursor.fetchone()[0]
            return esAmbito

        def extraer_concepto_valor(string_id_concepto_value, str_tipo_valor): # return string numeric code
            qry = f"""
                SELECT val.value FROM values val INNER JOIN values v ON v.conceptid = val.conceptid 
                WHERE val.valuetype='{str_tipo_valor}' AND v.valueid='{string_id_concepto_value}'
            """
            with connection.cursor() as cursor:
                cursor.execute(qry)
                valorConcepto = cursor.fetchone()
            if valorConcepto is not None: 
                return valorConcepto[0]
            else:
                return '00'

        def extraer_datos_recurso(string_id_recurso): # Return ['graph_id', principaluser_id]
            if string_id_recurso is not None:
                qry = f"""
                    SELECT graphid::text, principaluser_id FROM resource_instances 
                        WHERE resourceinstanceid ='{string_id_recurso}'
                """
                cursor = connection.cursor()
                cursor.execute(qry)
                datos_recurso = list(cursor.fetchone())
                cursor.close()
            else:
                datos_recurso = []
            return datos_recurso

        def extraer_etaId(usuario_id): # Return 'recurso_eta_id'
            eta_grp_ndeId = '263826f2-f4d9-11ef-822e-0242ac120006'
            qry = f"""
                SELECT t.resourceinstanceid AS etaid, ug.eta_grpid AS etagrp FROM tiles t 
                JOIN (
                    SELECT user_id, ((MAX(group_id) / 100) * 100) AS eta_grpid 
                    FROM auth_user_groups WHERE group_id > 9999 GROUP BY user_id) ug 
                ON t.tiledata['{eta_grp_ndeId}']::int = ug.eta_grpid 
                WHERE ug.user_id = {usuario_id} AND t.nodegroupid = '{eta_grp_ndeId}';
            """
            with connection.cursor() as cursor:
                cursor.execute(qry)
                etaNdeId = cursor.fetchone()
            if etaNdeId is not None :
                return etaNdeId
            else:
                return ['deb0ae78-56f1-4bcf-abb2-d02ffcdd33c1', 10100] # Actor Min.Culturas

        def insertar_actor(rsi_id, eta_tileid, eta_actorid, eta_ndeid, GraphId, eta_ndegrpid):
            ontProp = 'ac41d9be-79db-4256-b368-2f4559cfbe55'
            actorGraphId = '72420da0-db59-4048-aea2-03b0bc5cdcdf'

            try:
                qry = f"""
					INSERT INTO resource_x_resource (
						resourcexid, relationshiptype,
						resourceinstanceidfrom,
						resourceinstanceidto,
						modified, created, inverserelationshiptype,
						tileid,
						nodeid,
						resourceinstancefrom_graphid,
						resourceinstanceto_graphid
					)
					VALUES (
						gen_random_uuid(), '{ontProp}',
						'{rsi_id}',
						'{eta_actorid}',
						now(), now(), '{ontProp}',
						'{eta_tileid}',
						'{eta_ndeid}',
						'{GraphId}',
						'{actorGraphId}'
					) returning resourcexid
				"""
                with connection.cursor() as cursor:
                    cursor.execute(qry)
                    nvo_resxid = cursor.fetchone()[0]
                    qry2 = f"""
                        UPDATE tiles SET tiledata = jsonb_set(
                            tiledata, 
                            '{{{eta_ndeid}}}', '[{{
                                "resourceId": "{eta_actorid}", 
                                "ontologyProperty": "{ontProp}", 
                                "resourceXresourceId": "{nvo_resxid}", 
                                "inverseOntologyProperty": "{ontProp}"
                                }}]' 
                            )
                        WHERE nodegroupid = '{eta_ndegrpid}'
                        AND resourceinstanceid = '{rsi_id}'
                        returning tiledata 
                    """
                    cursor.execute(qry2)
                    cursor.close()

            except Exception as e:
                self.logger.error(e,"\t*** Falla al insertar actor")

        def actualizar_codigo(MetaCodigo):
            #calculo de la secuencia
            nvo_codigo = [MetaCodigo['tipoM'],MetaCodigo['codU'], MetaCodigo['codAm'], MetaCodigo['codsA'], MetaCodigo['num']]
            #languages = models.Language.objects.all()
            #default_language = languages.get(code=settings.LANGUAGE_CODE)
            #return {default_language.code: {"value": '-'.join(nvo_codigo), "direction": default_language.default_direction}}
            return '-'.join(nvo_codigo)

        def extraer_num_siguiente(strCod_ndeId, strCod_ndegrpId, MetaCodigo): # Return int
            str_cod_num = '-'.join([MetaCodigo['tipoM'],MetaCodigo['codU'], MetaCodigo['codAm'], MetaCodigo['codsA']])
            qry = f"""
                SELECT MAX(regexp_match(tiledata->>'{strCod_ndeId}','-(\d+)$')) AS max_cod_num
                FROM tiles WHERE nodegroupid = '{strCod_ndegrpId}' 
                AND tiledata->>'{strCod_ndeId}' LIKE '{str_cod_num}%';
            """
            with connection.cursor() as cursor:
                cursor.execute(qry)
                ultimo_num = cursor.fetchone()
            if ultimo_num is not None and ultimo_num[0] is not None: 
                print(f"Siguiente número del código: {ultimo_num}")
                return str(int(ultimo_num[0][0]) + 1)
            else:
                return str(1)

        def extraer_metavalor(metavalor_ngId, metavalor_nId, resource_id, metavalor={}): # Retorna o actualiza metavalor dict
            metaValor_tiles = Tile.objects.filter(nodegroup_id = str(metavalor_ngId), resourceinstance_id = resource_id)
            metavalor_actual = {}
            if len(metaValor_tiles) > 0 :
                metaValor_tile = metaValor_tiles[0]
                if metavalor_nId in metaValor_tile.data and metaValor_tile.data[metavalor_nId] is not None:
                    metavalor_actual = eval(metaValor_tile.data[metavalor_nId])

            elif len(metaValor_tiles) == 0 and len(metavalor) > 0:
                metavalor_actual.update(metavalor)
                metaValor_tile = Tile.get_blank_tile_from_nodegroup_id(metavalor_ngId, resource_id, None)
                metaValor_tile.data[metavalor_nId] = str(metavalor_actual)
                metaValor_tile.save()

            return metavalor_actual

        def extraer_codigo(codigo_ngId, codigo_nId, resource_id): # Return str
            cod_num = ""
            codigo_tiles = Tile.objects.filter(nodegroup_id = str(codigo_ngId), resourceinstance_id = resource_id)
            if len(codigo_tiles) > 0 :
                for codi in codigo_tiles :
                    # Si el código es del tipo principal
                    codigo_tile = codi if 'c07032eb-b50a-4048-b18c-bde5e8e6ad6a' in codi.data.values() else codigo_tiles[0]
                if codigo_nId in codigo_tile.data and codigo_tile.data[codigo_nId] is not None:
                    #cod_num = str(codigo_tile.data[codigo_nId]['es']['value'])
                    cod_num = str(codigo_tile.data[codigo_nId])
                    cod_num = re.sub(r'^(.+)-','',cod_num)
                    if cod_num != '' : cod_num = cod_num.rjust(6,'0')
            return cod_num

        try:
            # procedimiento para definir los metadatos

            metaCodlugar = {'tipoM': 'PM', 'codU': '000000', 'num': '000000'}
            metaCodIden = {'codAm': '00', 'codsA': '00'}
            metaGrupo = {'grp_eta': 10100, 'sufijo_grp_model': 0}
            datos_recurso = extraer_datos_recurso(tile.resourceinstance_id) # ['graph_id', principaluser_id]
            etaActor_id_grp = extraer_etaId(datos_recurso[1])
            etaActor_id = etaActor_id_grp[0]
            metaGrupo.update({'grp_eta': etaActor_id_grp[1]})
            es_patrimonio = True
            match datos_recurso[0]:
                case '72420da0-db59-4048-aea2-03b0bc5cdcdf' : # Actores
                    es_patrimonio = False
                    metaGrupo.update({'sufijo_grp_model': 17})

                case '6afc4e9e-727e-4152-8084-309267752650' : # Medios
                    es_patrimonio = False
                    metaGrupo.update({'sufijo_grp_model': 16})

                case "2bf240e6-718e-44f0-8073-4962d84931fe" : # Si el modelo es del tipo actividades
                    metaGrupo.update({'sufijo_grp_model': 15})

                case '76331d8a-4d85-11ee-a708-ffcd06c08a5d': # Si el modelo es del tipo arqueologico
                    metaGrupo.update({'sufijo_grp_model': 14})

                case '9b0e22c7-5c56-4be6-aa89-ae2632ba28e8': # Si el modelo es del tipo inmaterial
                    metaCodlugar.update({'tipoM': 'PI'})
                    metaGrupo.update({'sufijo_grp_model': 13})

                case '10b03fec-4d85-11ee-a708-ffcd06c08a5d': # Si el modelo es del tipo bienes muebles
                    metaGrupo.update({'sufijo_grp_model': 12})

                case '8ea3bf67-89ce-474c-b3cb-6dd5ee3d6198': # Si el modelo es del tipo bienes inmuebles
                    metaCodlugar.update({'num': 'D00M00L00'})
                    metaGrupo.update({'sufijo_grp_model': 11})

            metaIden_ndegrp = self.config['meta_identificacion']['ndegrpId']
            metaIden_nde = self.config['meta_identificacion']['ndeId']
            lugarAmplio_nde = self.config['lugar_amplio']['ndeId']
            metaIden = {}
            tup_depto = ()
            if es_patrimonio:
                # procedimiento para fijar el el actor administrativo
                eta_ndegrpId = self.config['eta']['ndegrpId']
                eta_ndeId = self.config['eta']['ndeId']
                etaTile = Tile.objects.filter(nodegroup_id=str(eta_ndegrpId), resourceinstance_id=tile.resourceinstance_id)
                if len(etaTile) == 0:
                    eta_tile = Tile.get_blank_tile_from_nodegroup_id(eta_ndegrpId, tile.resourceinstance_id, None)
                    eta_tile.save()
                    insertar_actor(tile.resourceinstance_id, str(eta_tile.tileid), etaActor_id, eta_ndeId, datos_recurso[0], eta_ndegrpId)

                metaLugar_ndegrp = self.config['meta_ubicacion']['ndegrpId']
                metaLugar_nde = self.config['meta_ubicacion']['ndeId']
                codigo_ndegrp = self.config['codigo']['ndegrpId']
                codigo_nde = self.config['codigo']['ndeId']
                codigo_num = metaCodlugar['num']

                # Código de lugar a meta lugar
                if str(tile.nodegroup_id) == self.config['lugar']['ndegrpId']:
                    lugar_value = tile.data[self.config["lugar"]["ndeId"]]
                    if lugar_value is not None : 
                        lugar_cod_value = extraer_concepto_valor(lugar_value, "hiddenLabel")
                        if len(lugar_cod_value) > 2 : 
                            lugar_cod_value = lugar_cod_value.ljust(5,'0')
                            lugar_cod_value = lugar_cod_value.rjust(6,'0')
                        else:
                            lugar_cod_value = lugar_cod_value.ljust(6,'0')
                        metaCodlugar.update(codU = lugar_cod_value)
                        # establecer lugar extendido
                        tup_depto = extraer_depto(lugar_cod_value[0:2])
                        print("\t*** tup_depto: ",tup_depto)
                    else:
                        tup_depto = extraer_depto('00')

                    if len(tup_depto) > 0 and tup_depto[0] is not None: tile.data[self.config['lugar_amplio']['ndeId']] = [str(tup_depto[0])]
                    
                    # extraer/crear número del código
                    num_anterior = extraer_codigo(codigo_ndegrp, codigo_nde, tile.resourceinstance_id)
                    if num_anterior != '' : metaCodlugar.update({'num': num_anterior})
                    
                    tile.data[metaLugar_nde] = str(metaCodlugar)

                # Ámbito a meta identificacion y código si es el caso
                if str(tile.nodegroup_id) == metaIden_ndegrp and codigo_ndegrp == metaIden_ndegrp :
                    metalugar = extraer_metavalor(metaLugar_ndegrp, metaLugar_nde, tile.resourceinstance_id)
                    if 'tipoM' in metalugar: metaCodlugar.update({'tipoM': metalugar['tipoM']})
                    if 'codU' in metalugar: metaCodlugar.update({'codU': metalugar['codU']})
                    if 'num' in metalugar: metaCodlugar.update({'num': metalugar['num']})
                    alcance_cod_values = tile.data[self.config["alcance"]["ndeId"]]
                    if alcance_cod_values is not None:
                        match len(alcance_cod_values):
                            case 0:
                                metaCodIden = {'codAm': '00', 'codsA': '00'}
                            case 1:
                                metaCodIden = {'codAm': extraer_concepto_valor(alcance_cod_values[0], "hiddenLabel").rjust(2,'0'), 'codsA': '00'}
                                if es_ambito(alcance_cod_values[0]) == '1':
                                    metaCodIden = {'codAm': extraer_concepto_valor(alcance_cod_values[0], "hiddenLabel").rjust(2,'0'), 'codsA': '00'}
                                else:
                                    metaCodIden = {'codAm': '00', 'codsA': extraer_concepto_valor(alcance_cod_values[0], "hiddenLabel").rjust(2,'0')}
                            case _ if len(alcance_cod_values) > 1:  # Falta completar validación de ámbito y subambito
                                if es_ambito(alcance_cod_values[0]) == '1':
                                    metaCodIden = {'codAm': extraer_concepto_valor(alcance_cod_values[0], "hiddenLabel").rjust(2,'0'), 'codsA':
                                        extraer_concepto_valor(alcance_cod_values[1], "hiddenLabel").rjust(2,'0')}
                                else:
                                    metaCodIden = {'codAm': extraer_concepto_valor(alcance_cod_values[1], "hiddenLabel").rjust(2,'0'), 'codsA':
                                        extraer_concepto_valor(alcance_cod_values[0], "hiddenLabel").rjust(2,'0')}
                    metacodigo = metaCodlugar | metaCodIden
                    metaIden.update(metaCodIden)
                    metaIden.update(metaGrupo)
 
                    # Procesado del número del código
                    if codigo_nde in tile.data and tile.data[codigo_nde] is not None :
                        codigo_num = str(tile.data[codigo_nde])
                        codigo_num = re.sub(r'^(.+)-','',codigo_num)

                    if "?" in re.sub(r'^(.+)-','',codigo_num) or codigo_num == '000000':
                        metacodigo.update(num = extraer_num_siguiente(codigo_nde, codigo_ndegrp, metacodigo).rjust(6,'0')) 
                    else:
                        metacodigo.update(num = codigo_num.rjust(6,'0'))   

                    tile.data[self.config['codigo']['ndeId']] = actualizar_codigo(metacodigo)     

                    # insertar permisos de ficha
                    metaiden = extraer_metavalor(metaIden_ndegrp, metaIden_nde, tile.resourceinstance_id)
                    if "grp_eta" not in metaiden :
                        establecer_grupos(tile.resourceinstance_id, metaGrupo['grp_eta'], metaGrupo['sufijo_grp_model'])
                    print("\t*** metacodigo : ", metacodigo)
                    metaIden.update(metaGrupo)
                    tile.data[metaIden_nde] = str(metaIden)
            
            if str(tile.nodegroup_id) == self.config['nombre']['ndegrpId'] and not es_patrimonio:
                print("\t*** insertar permisos de ficha complementaria")
                metaIden = extraer_metavalor(metaIden_ndegrp, metaIden_nde, tile.resourceinstance_id)
                if "grp_eta" not in metaIden :
                    establecer_grupos(tile.resourceinstance_id, metaGrupo['grp_eta'], metaGrupo['sufijo_grp_model'])
                    metaIden = extraer_metavalor(metaIden_ndegrp, metaIden_nde, tile.resourceinstance_id, metaGrupo)

            print("\t***   metaIden : ", metaIden,"\n")
            print("\t*** Nvo. map_popup: ",actualizar_descriptors(tile.resourceinstance_id)[0])
            return
         
        except Exception as e:
            self.logger.error(e,"\t*** Falla al fijar los datos iniciales")
            return
        
    def save(self, tile, request, context=None):
        self.logger = logging.getLogger(__name__)
        print("\n\t*** llamado a la función datos_iniciales.save ***")
        self.componer_codigo(tile=tile, request=request, is_function_save_method=True)
        return

    # occurrs after Tile.save
    def post_save(self, *args, **kwargs):
        raise NotImplementedError

    def delete(self, tile, request):
        raise NotImplementedError

    def on_import(self, tile):
        self.componer_codigo(tile=tile, request=None, is_function_save_method=False)
        return

    # saves changes to the function itself
    def after_function_save(self, tile, request):
        raise NotImplementedError

