from django.shortcuts import render, redirect, get_object_or_404
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from decimal import Decimal, InvalidOperation
from django.db import models as db_models
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.core.serializers import serialize
from datetime import date, timedelta, datetime, time
from django.conf import settings
from django.utils.timezone import localtime, now, make_aware, is_naive
from calendar import monthrange
from django.utils import timezone
from django.db.models import Q
from collections import defaultdict
from django.template.loader import get_template
from django.http import HttpResponse
from weasyprint import HTML
import tempfile
from django.utils.dateparse import parse_date
from django.db.models import Sum

import re

from accounts import models
from .models import Movimiento, Cuenta, MovimientoFrecuente, Categoria, TransaccionRecurrente
from .forms import MovimientoForm, FiltroForm, FiltroMesAnioForm, FiltroCuentaForm, InformeCuentaForm, FiltroInformeCuentaForm
from .recurrentes import procesar_transacciones_recurrentes

import logging

logger = logging.getLogger(__name__)

@login_required
def dashboard(request):
    # Copiamos GET para poder modificarlo
    request.GET = request.GET.copy()

    hoy = now()

    form_fecha = FiltroMesAnioForm(request.GET or None)
    mes = hoy.month
    anio = hoy.year

    if form_fecha.is_valid():
        mes = int(form_fecha.cleaned_data['mes'])
        anio = int(form_fecha.cleaned_data['anio'])

    logger.info(f"dashboard: mes: {mes} / año: {anio}")

    inicio_mes = timezone.make_aware(datetime(anio, mes, 1))
    ultimo_dia = monthrange(anio, mes)[1]
    fin_mes = timezone.make_aware(datetime(anio, mes, ultimo_dia, 23, 59, 59))

    logger.info("Inicio mes %s. Último día: %s. Fin_mes: %s", inicio_mes,ultimo_dia,fin_mes)

    movimientos = Movimiento.objects.filter(usuario=request.user, fecha__range=(inicio_mes, fin_mes)).order_by('-fecha')

    favoritas = Cuenta.objects.filter(favorita='True').order_by('nombre')

    frecuentes = MovimientoFrecuente.objects.filter(usuario=request.user).order_by('cuenta_destino')
    
    logger.info("Mostrando la vista dashboard con %d movimientos y %d favoritas", movimientos.count(), favoritas.count())   
    logger.info("El usuario '%s' accedió a la vista principal", request.user.nombre if request.user.is_authenticated else "Anónimo") 

    form_filtro = FiltroForm(request.GET or None)

    if form_filtro.is_valid():
        busqueda = form_filtro.cleaned_data.get('buscar')
        if busqueda:
            # logger.warning(f"dashboard: busqueda=true {busqueda}")
            movimientos = movimientos.filter(
                db_models.Q(descripcion__icontains=busqueda) | db_models.Q(cuenta_origen__nombre__icontains=busqueda) | db_models.Q(cuenta_destino__nombre__icontains=busqueda)
            )

    return render(request, 'dashboard.html', {
        'movimientos': movimientos,
        'user': request.user,
        'form_filtro': form_filtro,
        'form_fecha': form_fecha,
        'favoritas': favoritas,
        'frecuentes': frecuentes,
        'mes': mes,
        'anio': anio,
        'fecha_inicio': inicio_mes.date().strftime('%Y-%m-%d'),
        'fecha_fin': fin_mes.date().strftime('%Y-%m-%d')
    })


@login_required
def buscatotal(request):
    # Copiamos GET para poder modificarlo
    request.GET = request.GET.copy()

    movimientos = Movimiento.objects.filter(usuario=request.user).order_by('-fecha')

    form_filtro = FiltroForm(request.GET or None)

    fecha_fin = timezone.localtime().strftime('%Y-%m-%d')
    fecha_inicio = timezone.localtime().date().replace(day=1).strftime('%Y-%m-%d')

    if form_filtro.is_valid():
        busqueda = form_filtro.cleaned_data.get('buscar')
        if busqueda:
            # logger.warning(f"dashboard: busqueda=true {busqueda}")
            movimientos = movimientos.filter(
                db_models.Q(descripcion__icontains=busqueda) | db_models.Q(cuenta_origen__nombre__icontains=busqueda) | db_models.Q(cuenta_destino__nombre__icontains=busqueda)
            )

    return render(request, 'buscatotal.html', {
        'movimientos': movimientos,
        'user': request.user,
        'form_filtro': form_filtro,
        'fecha_inicio': fecha_inicio,
        'fecha_fin': fecha_fin,
    })

# Movimientos


@login_required
def agregar_movimiento(request):
    if request.method == 'POST':
        form = MovimientoForm(request.POST)
        cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')

        form.fields['cuenta_origen'].queryset = cuentas
        form.fields['cuenta_destino'].queryset = cuentas

        if form.is_valid():
            movimiento = form.save(commit=False)
            movimiento.usuario = request.user
            movimiento.save()
            # Ajustar saldos de cuentas
            movimiento.cuenta_origen.saldo -= movimiento.monto
            movimiento.cuenta_origen.save()
            movimiento.cuenta_destino.saldo += movimiento.monto
            movimiento.cuenta_destino.save()
            messages.success(request, 'Movimiento registrado con éxito.')

            return redirect('dashboard')
    else:
        datos_iniciales = {}
        origen = request.GET.get('origen')
        destino = request.GET.get('destino')
        categoria = request.GET.get('categoria')
        descripcion = request.GET.get('descripcion')
        monto_raw = request.GET.get('monto')

        # logger.warning(f"agregar_movimiento: origen: {origen}")
        # logger.warning(f"agregar_movimiento: destino: {destino}")
        # logger.warning(f"agregar_movimiento: categoria: {categoria}")
        # logger.warning(f"agregar_movimiento: descripcion: {descripcion}")
        # logger.warning(f"agregar_movimiento: monto_raw: {monto_raw}")

        try:
            if origen:
                datos_iniciales['cuenta_origen'] = Cuenta.objects.get(nombre=request.GET['origen'], usuario=request.user)
            if destino:
                datos_iniciales['cuenta_destino'] = Cuenta.objects.get(nombre=request.GET['destino'], usuario=request.user)
            if categoria:
                datos_iniciales['categoria'] = Categoria.objects.get(nombre=request.GET['categoria'])
            if descripcion:
                datos_iniciales['descripcion'] = descripcion
            if monto_raw:
                # Reemplaza coma por punto, elimina símbolos y espacios
                monto_normalizado = re.sub(r'[^\d,.-]', '', monto_raw).replace(',', '.')
                try:
                    datos_iniciales['monto'] = str(Decimal(monto_normalizado).quantize(Decimal('0.01')))
                except InvalidOperation:
                    pass  # Ignorar monto malformado
                    logger.warning(f"monto: mal formado {datos_iniciales['monto']}")
        except (Cuenta.DoesNotExist, InvalidOperation):
            pass

        # logger.warning(f"agregar_movimiento: datos iniciales: {datos_iniciales}")

        cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')

        form = MovimientoForm(initial=datos_iniciales)
        form.fields['cuenta_origen'].queryset = cuentas
        form.fields['cuenta_destino'].queryset = cuentas
    return render(request, 'agregar_movimiento.html', {'form': form})


@login_required
def editar_movimiento(request, movimiento_id):
    movimiento = get_object_or_404(Movimiento, id=movimiento_id, usuario=request.user)

    cuenta_original_origen = movimiento.cuenta_origen
    cuenta_original_destino = movimiento.cuenta_destino
    monto_original = movimiento.monto

    if request.method == 'POST':
        form = MovimientoForm(request.POST, instance=movimiento)

        cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')
        form.fields['cuenta_origen'].queryset = cuentas
        form.fields['cuenta_destino'].queryset = cuentas

        # Revertir efecto anterior
        # Cuenta origen

        cuenta_original_origen.saldo += monto_original
        cuenta_original_origen.save()

        # Cuenta destino

        cuenta_original_destino.saldo -= monto_original
        cuenta_original_destino.save()

        if form.is_valid():
            movimiento_nuevo = form.save(commit=False)

            # Aplicar nuevo efecto
            # con esta modificación, que lo deja como estaba originalmente, parece que está arreglado.
            # Si cambio el importe, se ajustan los saldos origen y destino apropiadamente.
            # Si cambia una de las cuentas, también queda bien reflelado.
            # Si intercambio las cuentas, es decir, la origen pasa a ser la destino y viceversa, también queda bien reflejado.

            movimiento_nuevo.usuario = request.user
            movimiento_nuevo.save()

            movimiento_nuevo.cuenta_origen.saldo -= movimiento_nuevo.monto  # ...tomar como valor del saldo el de la nueva cuenta.
            movimiento_nuevo.cuenta_origen.save()

            movimiento_nuevo.cuenta_destino.saldo += movimiento_nuevo.monto  # ...tomar como valor del saldo el de la nueva cuenta.
            movimiento_nuevo.cuenta_destino.save()

            return redirect('dashboard')
    else:
        form = MovimientoForm(instance=movimiento)

        cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')
        form.fields['cuenta_origen'].queryset = cuentas
        form.fields['cuenta_destino'].queryset = cuentas

    return render(request, 'editar_movimiento.html', {'form': form})


@login_required
def eliminar_movimiento(request, movimiento_id):
    movimiento = get_object_or_404(Movimiento, id=movimiento_id, usuario=request.user)
    if request.method == 'POST':
        # Revertir efecto del movimiento antes de borrarlo
        movimiento.cuenta_origen.saldo += movimiento.monto
        movimiento.cuenta_origen.save()
        movimiento.cuenta_destino.saldo -= movimiento.monto
        movimiento.cuenta_destino.save()
        movimiento.delete()
        return redirect('dashboard')

    return render(request, 'eliminar_movimiento.html', {'movimiento': movimiento})


@login_required
def duplicar_movimiento(request, movimiento_id):
    original = get_object_or_404(Movimiento, id=movimiento_id, usuario=request.user)

    # Crear una copia del movimiento sin guardar
    movimiento_nuevo = Movimiento(
        cuenta_origen=original.cuenta_origen,
        cuenta_destino=original.cuenta_destino,
        monto=original.monto,
        fecha=now(),
        descripcion=original.descripcion,
        usuario=request.user,
        categoria=original.categoria,
    )

    if request.method == 'POST':
        form = MovimientoForm(request.POST, instance=movimiento_nuevo)
        cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')
        form.fields['cuenta_origen'].queryset = cuentas
        form.fields['cuenta_destino'].queryset = cuentas
        if form.is_valid():
            nuevo = form.save(commit=False)
            nuevo.usuario = request.user
            nuevo.save()
            # Ajustar saldos
            nuevo.cuenta_origen.saldo -= nuevo.monto
            nuevo.cuenta_origen.save()
            nuevo.cuenta_destino.saldo += nuevo.monto
            nuevo.cuenta_destino.save()
            messages.success(request, 'Movimiento duplicado con éxito.')
            return redirect('dashboard')
    else:
        form = MovimientoForm(instance=movimiento_nuevo)
        cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')
        form.fields['cuenta_origen'].queryset = cuentas
        form.fields['cuenta_destino'].queryset = cuentas

    return render(request, 'duplicar_movimiento.html', {'form': form})

# Cuentas


@login_required
def listar_cuentas(request):
    cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')

    # desde aquí....
    form_filtro = FiltroForm(request.GET or None)

    fecha_fin = timezone.localtime().strftime('%Y-%m-%d')
    fecha_inicio = timezone.localtime().date().replace(day=1).strftime('%Y-%m-%d')

    if form_filtro.is_valid():
        busqueda = form_filtro.cleaned_data.get('buscar')
        if busqueda:
            cuentas = cuentas.filter(
                db_models.Q(nombre__icontains=busqueda)
            )
    # hasta aquí, hay que quitarlo

    # en el render hay que pasar solo cuentas.

    return render(request, 'listar_cuentas.html',
                  {'cuentas': cuentas,
                   'form_filtro': form_filtro,
                   'fecha_inicio': fecha_inicio,
                   'fecha_fin': fecha_fin
                   })


# API


def api_datos_completos(request):
    cuentas = list(Cuenta.objects.values('id', 'nombre', 'tipo', 'saldo', 'usuario__nombre', 'favorita', 'saldo_inicial'))
    categorias = list(Categoria.objects.values('id', 'nombre'))

    movimientos = [
        {
            'id': m.id,
            'usuario': m.usuario.nombre,
            'cuenta_origen': m.cuenta_origen.nombre,
            'cuenta_destino': m.cuenta_destino.nombre,
            'monto': float(m.monto),
            'fecha': m.fecha.isoformat(),
            'descripcion': m.descripcion,
            'categoria': m.categoria.nombre if m.categoria else None,
        }
        for m in Movimiento.objects.select_related('cuenta_origen', 'cuenta_destino', 'usuario', 'categoria')
    ]

    movs_frecuentes = [
        {
            'id': m.id,
            'nombre': m.nombre,
            'cuenta_origen': m.cuenta_origen.nombre,
            'cuenta_destino': m.cuenta_destino.nombre,
            'monto': float(m.monto),
            'categoria': m.categoria.nombre if m.categoria else None,
            'descripcion': m.descripcion,
            'usuario': m.usuario.nombre,
        }
        for m in MovimientoFrecuente.objects.select_related('cuenta_origen', 'cuenta_destino', 'usuario', 'categoria')
    ]

    movs_recurrentes = [
        {
            'id': m.id,
            'usuario': m.usuario.nombre,
            'cuenta_origen': m.cuenta_origen.nombre,
            'cuenta_destino': m.cuenta_destino.nombre,
            'monto': float(m.monto),
            'categoria': m.categoria.nombre if m.categoria else None,
            'descripcion': m.descripcion,
            'frecuencia': m.frecuencia,
            'dia_del_mes': m.dia_del_mes,
            'dia_semana': m.dia_semana,
            'semana': m.semana,
            'activa': m.activa,
        }
        for m in TransaccionRecurrente.objects.select_related('cuenta_origen', 'cuenta_destino', 'usuario', 'categoria')
    ]

    return JsonResponse({
        'cuentas': cuentas,
        'categorias': categorias,
        'movimientos': movimientos,
        'movimientos_frecuentes': movs_frecuentes,
        'transacciones_recurrentes': movs_recurrentes,
    }, safe=False)


@login_required
def ejecutar_recurrentes(request):
    procesar_transacciones_recurrentes()
    return redirect('dashboard')


@csrf_exempt
def api_ejecutar_recurrentes(request):
    token_enviado = request.headers.get('X-API-KEY') or request.GET.get('token')
    #token_enviado = request.POST.get('token')

    # Usar token dedicado para la API de informes
    if token_enviado != settings.RECURRENTES_API_TOKEN:
        logger.warning('api_ejecutar_recurrentes: intento no autorizado. Token: <%s>', token_enviado, extra={'ip': request.META.get('REMOTE_ADDR'), 'cuenta_id': request.GET.get('cuenta_id')})
        return JsonResponse({'error': 'No autorizado'}, status=403)

    procesar_transacciones_recurrentes(hoy=now().date())
    return JsonResponse({'ok': True, 'mensaje': 'Transacciones recurrentes ejecutadas'})


@login_required
def resumen_saldos_mensuales(request):
    cuentas = Cuenta.objects.filter(usuario=request.user).order_by('nombre')
    movimientos = Movimiento.objects.filter(usuario=request.user).order_by('fecha')

    # Diccionario: {(cuenta_id, año, mes): saldo}
    saldos_mensuales = defaultdict(lambda: 0)

    # Diccionario para llevar el saldo acumulado
    saldos_acumulados = {cuenta.id: float(cuenta.saldo_inicial) for cuenta in cuentas}

    # Obtener todos los meses presentes en movimientos
    meses_presentes = set()

    for mov in movimientos:
        anio = mov.fecha.year
        mes = mov.fecha.month
        clave = (mov.cuenta_origen.id, anio, mes)
        meses_presentes.add((anio, mes))

        # Resta al origen
        saldos_acumulados[mov.cuenta_origen.id] -= float(mov.monto)
        saldos_mensuales[clave] = saldos_acumulados[mov.cuenta_origen.id]

        # Suma al destino
        clave_dest = (mov.cuenta_destino.id, anio, mes)
        saldos_acumulados[mov.cuenta_destino.id] += float(mov.monto)
        saldos_mensuales[clave_dest] = saldos_acumulados[mov.cuenta_destino.id]

    # Ordenar meses
    meses_ordenados = sorted(meses_presentes)

    return render(request, 'resumen_saldos_mensuales.html', {
        'cuentas': cuentas,
        'meses': meses_ordenados,
        'saldos_mensuales': saldos_mensuales,
    })


# Informes


@login_required
def informe_cuenta(request):
    cuenta_id = request.GET.get('cuenta_id')

    cuenta = get_object_or_404(Cuenta, id=cuenta_id, usuario=request.user)

    logger.info('api_informe_cuenta: petición aceptada: %s', cuenta_id)

    form = FiltroInformeCuentaForm(request.GET or None)

    if form.is_valid():
        # obtener fechas desde GET o establecer por defecto (mes actual)
        hoy = timezone.localtime()
        primer_dia = hoy.replace(day=1)

        fecha_inicio_str = request.GET.get('fecha_inicio')
        fecha_fin_str = request.GET.get('fecha_fin')

        if fecha_inicio_str and fecha_fin_str:
            fecha_inicio = datetime.strptime(fecha_inicio_str, '%Y-%m-%d').date()
            fecha_fin = datetime.strptime(fecha_fin_str, '%Y-%m-%d').date()
        else:
            fecha_inicio = primer_dia
            fecha_fin = hoy

        # Convertir a datetime aware para evitar warnings
        fecha_inicio_dt = timezone.make_aware(datetime.combine(fecha_inicio, time.min))
        fecha_fin_dt = timezone.make_aware(datetime.combine(fecha_fin, time.max))

        # Saldo antes del periodo
        movimientos_previos = Movimiento.objects.filter(
            fecha__lt=fecha_inicio_dt,
        ).filter(
            cuenta_origen=cuenta
        ).aggregate(total_origen=Sum('monto')) or {'total_origen': 0}

        entradas_previas = Movimiento.objects.filter(
            fecha__lt=fecha_inicio_dt,
        ).filter(
            cuenta_destino=cuenta
        ).aggregate(total_destino=Sum('monto')) or {'total_destino': 0}

        saldo_inicial = (entradas_previas['total_destino'] or 0) - (movimientos_previos['total_origen'] or 0)
        saldo_inicial = saldo_inicial + cuenta.saldo_inicial

        movimientos = Movimiento.objects.filter(
            Q(cuenta_origen=cuenta) | Q(cuenta_destino=cuenta),
            fecha__range=(fecha_inicio_dt, fecha_fin_dt)
        ).order_by('fecha')

        # Cálculo de saldos después de cada movimiento
        saldo = saldo_inicial

        movimientos_con_saldo = []
        for mov in movimientos:
            if mov.cuenta_origen == cuenta:
                saldo -= mov.monto
                tipo = 'Gasto'
            elif mov.cuenta_destino == cuenta:
                saldo += mov.monto
                tipo = 'Ingreso'

            movimientos_con_saldo.append({
                'id': mov.id,
                'fecha': mov.fecha,
                'descripcion': mov.descripcion,
                'monto': mov.monto,
                'tipo': tipo,
                'desde': mov.cuenta_origen.nombre if mov.cuenta_origen else 'N/A',
                'hasta': mov.cuenta_destino.nombre if mov.cuenta_destino else 'N/A',
                'saldo_despues': saldo,
            })

        # Renderizado del PDF o del HTML como prefieras
        context = {
            'cuenta': cuenta,
            'movimientos_con_saldo': movimientos_con_saldo,
            'fecha_inicio': fecha_inicio_str,
            'fecha_fin': fecha_fin_str,
            'saldo_inicial': saldo_inicial,
        }

        return render(request, 'informe_cuenta.html', context)


@login_required
def informe_cuenta_pdf(request):
    cuenta_id = request.GET.get('cuenta_id')

    cuenta = get_object_or_404(Cuenta, id=cuenta_id, usuario=request.user)

    # obtener fechas desde GET o establecer por defecto (mes actual)
    hoy = timezone.localtime()
    primer_dia = hoy.replace(day=1)

    fecha_inicio_str = request.GET.get('fecha_inicio')
    fecha_fin_str = request.GET.get('fecha_fin')

    if fecha_inicio_str and fecha_fin_str:
        fecha_inicio = datetime.strptime(fecha_inicio_str, '%Y-%m-%d').date()
        fecha_fin = datetime.strptime(fecha_fin_str, '%Y-%m-%d').date()
    else:
        fecha_inicio = primer_dia
        fecha_fin = hoy

    # Convertir a datetime aware para evitar warnings
    fecha_inicio_dt = timezone.make_aware(datetime.combine(fecha_inicio, time.min))
    fecha_fin_dt = timezone.make_aware(datetime.combine(fecha_fin, time.max))

    # Saldo antes del periodo
    movimientos_previos = Movimiento.objects.filter(
        fecha__lt=fecha_inicio_dt,
    ).filter(
        cuenta_origen=cuenta
    ).aggregate(total_origen=Sum('monto')) or {'total_origen': 0}

    entradas_previas = Movimiento.objects.filter(
        fecha__lt=fecha_inicio_dt,
    ).filter(
        cuenta_destino=cuenta
    ).aggregate(total_destino=Sum('monto')) or {'total_destino': 0}

    saldo_inicial = (entradas_previas['total_destino'] or 0) - (movimientos_previos['total_origen'] or 0)
    saldo_inicial = saldo_inicial + cuenta.saldo_inicial

    movimientos = Movimiento.objects.filter(
        Q(cuenta_origen=cuenta) | Q(cuenta_destino=cuenta),
        fecha__range=(fecha_inicio_dt, fecha_fin_dt)
    ).order_by('fecha')

    saldo = saldo_inicial
    historial = []

    for mov in movimientos:
        if mov.cuenta_origen == cuenta:
            saldo -= mov.monto
            tipo = 'Gasto'
        elif mov.cuenta_destino == cuenta:
            saldo += mov.monto
            tipo = 'Ingreso'
        else:
            tipo = 'Otro'

        historial.append({
            'fecha': mov.fecha,
            'desde': mov.cuenta_origen.nombre if mov.cuenta_origen else 'N/A',
            'hasta': mov.cuenta_destino.nombre if mov.cuenta_destino else 'N/A',
            'descripcion': mov.descripcion,
            'monto': mov.monto,
            'tipo': tipo,
            'saldo_despues': saldo,
        })

    template = get_template('informe_cuenta_pdf.html')
    html = template.render({'cuenta': cuenta, 'saldo_inicial': saldo_inicial, 'fecha_inicio': fecha_inicio_str, 'fecha_fin': fecha_fin_str, 'historial': historial})

    with tempfile.NamedTemporaryFile(delete=True) as output:
        HTML(string=html).write_pdf(output.name)

        output.seek(0)
        response = HttpResponse(output.read(), content_type='application/pdf')
        response['Content-Disposition'] = f'filename=informe_{cuenta.nombre}.pdf'
        return response
