from django.shortcuts import render, redirect
from django.http import JsonResponse, HttpResponse
from django.conf import settings
from django.db.models import Prefetch
from django.views.decorators.http import require_http_methods
from django.db import IntegrityError, transaction 
import json
import os

from login.models import Usuario, Area, Gerencia
from .models import PermisoArea, PermisoUrl, Solicitud, Url 


def usuarios_view(request):

    if 'user_id' not in request.session or request.session.get('user_tipo') == 2 or request.session.get('user_tipo') == 3:
        # Redirigir al login si no hay sesión
        return redirect('/')
    
    return render(request, 'usuarios.html')


def get_usuarios(request):
    """
    Retorna una lista de usuarios con sus permisos, MÁS las listas maestras de 
    Áreas (incluyendo Gerencia) y URLs. Optimizada con Prefetch(to_attr) y list comprehensions.
    """
    try:
        # --- 1. Definición de Prefetching (Optimizando N+1 Queries) ---
        
        # Prefetch para PermisoArea: SOLO TRAE PERMISOS ACTIVOS
        permiso_area_prefetch = Prefetch(
            'permisos_area',
            queryset=PermisoArea.objects.filter(estado=1).select_related('area'),
            to_attr='_permisos_area'
        )
        
        # Prefetch para PermisoUrl: SOLO TRAE PERMISOS ACTIVOS
        permiso_url_prefetch = Prefetch(
            'permisos_url', 
            queryset=PermisoUrl.objects.filter(estado=1).select_related('url'),
            to_attr='_permisos_url'
        )
        
        # Prefetch para Solicitudes
        solicitudes_prefetch = Prefetch(
            'solicitudes', 
            queryset=Solicitud.objects.select_related('solicitud_url', 'solicitud_area'),
            to_attr='_solicitudes'
        )

        # 2. Obtener usuarios (select_related para Area y Gerencia principal)
        usuarios = Usuario.objects.select_related('area', 'area__gerencia').prefetch_related(
            permiso_area_prefetch,
            permiso_url_prefetch,
            solicitudes_prefetch
        ).all().order_by('nombre')
        
        # --- 3. Obtener Listas Maestras ---
        
        # A. Áreas Maestras
        areas_maestras = Area.objects.select_related('gerencia').filter(urls__isnull=False).distinct().order_by('nombre')
        maestro_areas_data = [{
            'id': a.id,
            'nombre': a.nombre,
            'gerencia_id': a.gerencia_id, 
            'gerencia_nombre': a.gerencia.nombre if a.gerencia else 'Sin Gerencia',
        } for a in areas_maestras]

        # B. URLs Maestras (incluimos info de área para poder agrupar en el frontend)
        urls_maestras = Url.objects.select_related('area', 'area__gerencia').all().order_by('nombre')
        maestro_urls_data = [{
            'id': u.id,
            'nombre': u.nombre,
            'url': u.url,
            'area_id': u.area_id,
            'area_nombre': u.area.nombre if u.area else 'Sin área',
            'gerencia_id': u.area.gerencia_id if u.area and u.area.gerencia else None,
            'gerencia_nombre': u.area.gerencia.nombre if u.area and u.area.gerencia else 'Sin gerencia',
        } for u in urls_maestras]

        # --- 4. Serializar datos de usuarios individuales (Optimizada) ---
        
        usuarios_data = []
        for u in usuarios:
            # Serialización eficiente de Permisos de Área
            permisos_area_data = [{
                'area_nombre': pa.area.nombre,
                'area_id': pa.area.id,
                'estado': pa.estado,
            } for pa in u._permisos_area] 

            # Serialización eficiente de Permisos de URL
            permisos_url_data = [{
                'url_nombre': pu.url.nombre,
                'url_id': pu.url.id,
                'url': pu.url.url,
                'estado': pu.estado,
            } for pu in u._permisos_url]

            # Serialización eficiente de Solicitudes
            solicitudes_data = [{
                'id': s.id,
                'url_nombre': s.solicitud_url.nombre if s.solicitud_url else 'N/A',
                'area_nombre': s.solicitud_area.nombre if s.solicitud_area else 'N/A',
                'justificacion': s.justificacion,
                'estado_codigo': s.estado,
                'estado_display': s.get_estado_display(),
            } for s in u._solicitudes]
            
            # Serialización de datos base del usuario
            usuarios_data.append({
                'id': u.id,
                'nombre': u.nombre,
                'correo': u.correo,
                'cc': u.cc,
                'estado': u.estado,
                'tipo': u.tipo,
                'pais': u.pais,
                'ciudad': u.ciudad,
                'area_id': u.area_id, 
                'area': u.area.nombre if u.area else 'Sin área',
                # ⭐ Importante: Enviamos el ID numérico de Gerencia y el nombre
                'gerencia_id': u.area.gerencia_id if u.area and u.area.gerencia else None, 
                'gerencia': u.area.gerencia.nombre if u.area and u.area.gerencia else 'Sin gerencia',
                'img': f"{settings.MEDIA_URL}profile/{u.img}" if u.img else f"{settings.MEDIA_URL}profile/default.png",
                
                'permisos_area': permisos_area_data,
                'permisos_url': permisos_url_data,
                'solicitudes': solicitudes_data,
            })
            
        # 5. Retornar la respuesta
        return JsonResponse({
            'usuarios': usuarios_data,
            'maestro_areas': maestro_areas_data, 
            'maestro_urls': maestro_urls_data, 
        })
        
    except Exception as e:
        print(f"Error al obtener usuarios: {e}")
        return JsonResponse({'usuarios': [], 'maestro_areas': [], 'maestro_urls': [], 'error': str(e)}, status=500)


### API Views (POST)
@require_http_methods(["POST"])
@transaction.atomic
def update_usuario(request):
    """
    Actualiza la información básica y el área de un usuario.
    El campo 'gerencia' solo se usa para validación cruzada del área.
    """
    try:
        data = json.loads(request.body)
    except json.JSONDecodeError:
        return JsonResponse({'error': 'Formato JSON inválido.'}, status=400)

    user_cc = data.get('cc')
    if not user_cc:
        return JsonResponse({'error': 'CC (cédula) del usuario es requerido.'}, status=400)

    # 1. Buscar el usuario
    try:
        usuario = Usuario.objects.get(cc=user_cc)
    except Usuario.DoesNotExist:
        return JsonResponse({'error': f'Usuario con CC {user_cc} no encontrado.'}, status=404)

    # 2. Asignar y validar campos directos
    try:
        usuario.nombre = data.get('nombre', usuario.nombre)
        usuario.correo = data.get('correo', usuario.correo) 
        usuario.tipo = int(data.get('tipo', usuario.tipo))
        
        estado_nuevo = int(data.get('estado', usuario.estado))
        if estado_nuevo not in [0, 1]:
            raise ValueError("El campo estado debe ser 0 o 1.")
        usuario.estado = estado_nuevo
        
        # --- Lógica de Manejo de Gerencia (Solo para obtener el gerencia_id para validación de Área) ---
        gerencia_key_raw = data.get('gerencia') # Viene la clave de texto (e.g., "GESTION_HUMANA")
        gerencia_id_nueva = None
        
        if gerencia_key_raw:
            # Buscar el ID de Gerencia real en la BD usando el nombre/clave que envía el front
            # Asumimos que la clave de texto en MAESTRO_GERENCIAS_AREAS coincide con el nombre en la tabla Gerencia
            gerencia_obj = Gerencia.objects.filter(nombre__iexact=gerencia_key_raw).first()
            if gerencia_obj:
                 gerencia_id_nueva = gerencia_obj.id
            else:
                 # Si no se encuentra por nombre/clave, intentar como ID numérico si el front lo envió así
                 try:
                     gerencia_id_test = int(gerencia_key_raw)
                     gerencia_obj = Gerencia.objects.filter(id=gerencia_id_test).first()
                     if gerencia_obj:
                         gerencia_id_nueva = gerencia_obj.id
                 except ValueError:
                     # Si no es INT ni un nombre de Gerencia conocido, gerencia_id_nueva sigue siendo None
                     pass

        # ⭐ Importante: NO hay lógica de "usuario.area = None" aquí, ya que el cambio de Gerencia es visual/filtro.
        # El área solo cambia si se selecciona un nuevo valor en el paso 3.
        
    except ValueError as e:
        return JsonResponse({'error': f'Error de validación: {e}'}, status=400)
    except Exception as e:
         return JsonResponse({'error': f'Error al actualizar campos básicos: {e}'}, status=500)

    # 3. Actualizar la clave foránea 'area' (usando area_id directamente)
    area_id_raw = data.get('area') 
    area_id = None
    
    # 3.1 Procesamiento del input de área
    if area_id_raw is not None and area_id_raw != "":
        try:
            area_id = int(area_id_raw)
        except ValueError:
             return JsonResponse({'error': 'El campo área debe ser un número entero.'}, status=400)

    # 3.2 Asignación y validación del área
    if area_id:
        try:
            # Validar que el Area exista
            area_obj = Area.objects.filter(id=area_id).select_related('gerencia').first()
            if not area_obj:
                return JsonResponse({'error': f'El ID de área {area_id} no existe.'}, status=400)
            
            # ⭐ Validación de pertenencia a la gerencia seleccionada (si se seleccionó una)
            if gerencia_id_nueva and area_obj.gerencia_id != gerencia_id_nueva:
                 return JsonResponse({'error': f'El área {area_obj.nombre} no pertenece a la gerencia seleccionada ({gerencia_id_nueva}).'}, status=400)

            usuario.area_id = area_id # Asignación directa de ID
        except Exception as e:
            return JsonResponse({'error': f'Error al asignar el área: {e}'}, status=500)
    else:
        # Si area_id es None (se envió null, vacío, o 0), desvinculamos. (Permitido por el modelo)
        usuario.area = None
    
    # 4. Guardar los cambios (Manejo de errores de base de datos)
    try:
        usuario.save()
    except IntegrityError:
        # Captura específica para errores de unicidad (ej. correo duplicado)
        return JsonResponse({'error': 'El correo electrónico o CC ya están en uso por otro usuario.'}, status=400)
    except Exception as e:
        print(f"Error al guardar el usuario en DB: {e}")
        return JsonResponse({'error': 'Error interno del servidor al guardar los cambios.'}, status=500)

    # 5. Respuesta exitosa
    return JsonResponse({'message': 'Usuario actualizado exitosamente.', 'cc': usuario.cc})


@require_http_methods(["POST"])
@transaction.atomic
def update_area_perms(request):
    """
    Recibe JSON para actualizar Permisos de Área y utiliza update_or_create 
    dentro de una transacción atómica para garantizar la consistencia.
    """
    try:
        data = json.loads(request.body)
    except json.JSONDecodeError:
        return JsonResponse({'error': 'Formato JSON inválido.'}, status=400)

    user_cc = data.get('cc')
    areas = data.get('areas')
    if not user_cc or areas is None:
        return JsonResponse({'error': 'Parámetros missing: cc y areas son requeridos.'}, status=400)

    try:
        usuario = Usuario.objects.get(cc=user_cc)
    except Usuario.DoesNotExist:
        return JsonResponse({'error': f'Usuario con CC {user_cc} no encontrado.'}, status=404)

    changes = []
    url_changes = []
    with transaction.atomic():
        # Primero, obtenemos todos los permisos actuales del usuario
        permisos_actuales = PermisoArea.objects.filter(usuario=usuario)
        
        # Creamos un diccionario para búsqueda rápida
        permisos_dict = {p.area_id: p for p in permisos_actuales}
        
        # Procesamos las áreas recibidas
        for a in areas:
            try:
                area_id = int(a.get('id'))
                checked = bool(a.get('checked'))
                estado_deseado = 1 if checked else 0
            except Exception as e:
                print(f"Error procesando área: {a}, error: {e}")
                continue 

            try:
                area_obj = Area.objects.get(id=area_id)
            except Area.DoesNotExist:
                print(f"Área no encontrada: {area_id}")
                continue

            # Si el permiso existe, actualizamos su estado
            if area_id in permisos_dict:
                permiso = permisos_dict[area_id]
                if permiso.estado != estado_deseado:
                    permiso.estado = estado_deseado
                    permiso.save()
                    changes.append({'area_id': area_id, 'action': 'updated', 'estado': estado_deseado})
            else:
                # Si no existe, creamos uno nuevo
                permiso = PermisoArea.objects.create(
                    usuario=usuario,
                    area=area_obj,
                    estado=estado_deseado
                )
                changes.append({'area_id': area_id, 'action': 'created', 'estado': estado_deseado})
            
            # Si se desactiva el permiso de área, desactivar todas las URLs relacionadas
            if estado_deseado == 0:
                permisos_url_actualizados = PermisoUrl.objects.filter(
                    usuario=usuario,
                    url__area_id=area_id,
                    estado=1
                )

                urls_afectadas = list(permisos_url_actualizados.values_list('url_id', flat=True))
                if urls_afectadas:
                    permisos_url_actualizados.update(estado=0)
                    for url_id in urls_afectadas:
                        url_changes.append({
                            'url_id': url_id,
                            'area_id': area_id,
                            'action': 'updated',
                            'estado': 0
                        })

            
    # Registramos todos los cambios en la respuesta
    return JsonResponse({
        'message': 'Permisos de área actualizados.',
        'changes': changes,
        'url_changes': url_changes,
        'debug_info': {
            'areas_recibidas': areas,
            'cambios_realizados': changes,
            'urls_actualizadas': url_changes
        }
    })


@require_http_methods(["POST"])
@transaction.atomic
def update_url_perms(request):
    """
    Recibe JSON para actualizar Permisos de URL y utiliza update_or_create
    dentro de una transacción atómica para garantizar la consistencia.
    """
    try:
        data = json.loads(request.body)
    except json.JSONDecodeError:
        return JsonResponse({'error': 'Formato JSON inválido.'}, status=400)

    user_cc = data.get('cc')
    urls = data.get('urls')
    if not user_cc or urls is None:
        return JsonResponse({'error': 'Parámetros missing: cc y urls son requeridos.'}, status=400)

    try:
        usuario = Usuario.objects.get(cc=user_cc)
    except Usuario.DoesNotExist:
        return JsonResponse({'error': f'Usuario con CC {user_cc} no encontrado.'}, status=404)

    changes = []
    with transaction.atomic():
        # Primero, obtener todas las URLs existentes para el usuario
        permisos_existentes = PermisoUrl.objects.filter(usuario=usuario)
        permisos_dict = {str(p.url_id): p for p in permisos_existentes}

        # Procesar las URLs recibidas
        for u in urls:
            try:
                url_id = int(u.get('id'))
                checked = bool(u.get('checked'))
                estado_deseado = 1 if checked else 0
            except Exception as e:
                print(f"Error procesando URL: {u}, error: {e}")
                continue

            try:
                url_obj = Url.objects.get(id=url_id)
            except Url.DoesNotExist:
                print(f"URL no encontrada: {url_id}")
                continue

            # Si el permiso existe, actualizar su estado
            if str(url_id) in permisos_dict:
                permiso = permisos_dict[str(url_id)]
                if permiso.estado != estado_deseado:
                    permiso.estado = estado_deseado
                    permiso.save()
                    changes.append({'url_id': url_id, 'action': 'updated', 'estado': estado_deseado})
            else:
                # Si no existe y se está activando (checked=True), crear uno nuevo
                # Si se está desactivando (checked=False) y no existe, no crear el permiso
                if checked:
                    permiso = PermisoUrl.objects.create(
                        usuario=usuario,
                        url=url_obj,
                        estado=estado_deseado
                    )
                    changes.append({'url_id': url_id, 'action': 'created', 'estado': estado_deseado})
                # Si checked=False y no existe el permiso, no hacer nada (ya está desactivado)

    return JsonResponse({
        'message': 'Permisos de URL actualizados.',
        'changes': changes,
        'debug_info': {
            'urls_recibidas': urls,
            'cambios_realizados': changes
        }
    })