import os
import threading
import uuid
import webbrowser
from functools import wraps

from flask import Flask, request, jsonify, make_response, send_file, send_from_directory, session
from werkzeug.security import generate_password_hash, check_password_hash
from dotenv import load_dotenv

import db
from prestamos_logic import (
    calcular_credit_score,
    calcular_cuotas,
    calcular_estadisticas_admin,
    calcular_fecha_vencimiento,
    calcular_resumen_deuda,
    generar_acuerdo_pdf,
    generar_pagare_pdf,
    hash_acuerdo,
    validar_usura,
    sanitizar_clausulas,
    tasa_referencia_mercado,
    PERIODOS_DASHBOARD,
)
from validacion_ocr import validar_documento_identidad
from envio_email import enviar_correo, smtp_configurado

MONEDAS_VALIDAS = ("COP", "USD")
TIPOS_TASA_VALIDOS = ("mes_vencido", "efectiva_anual")
TIPOS_PERIODO_VALIDOS = ("mes", "anio")
TIPOS_DOCUMENTO_VALIDOS = ("CC", "CE", "PA")

# Servicios adicionales que PrestaRed ofrece al prestamista al crear el préstamo. El costo se
# calcula siempre en el servidor (nunca se confía en un costo enviado por el cliente).
COSTO_SEGURO_PCT = 5  # % del monto inicial del préstamo, en la misma moneda del préstamo
COSTO_WHATSAPP_COP = 100000  # tarifa única en COP, fija sin importar la moneda del préstamo ni el plazo

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FRONTEND_DIR = os.path.join(BASE_DIR, "frontend")
UPLOADS_DIR = os.path.join(BASE_DIR, "uploads", "firmas")
os.makedirs(UPLOADS_DIR, exist_ok=True)

EXTENSIONES_DOCUMENTO_VALIDAS = (".jpg", ".jpeg", ".pdf")
TAMANO_MAXIMO_DOCUMENTO = 8 * 1024 * 1024  # 8 MB

load_dotenv(os.path.join(BASE_DIR, ".env"))
# Respaldo: si el .env se ubicó junto a app.py (en vez de en la carpeta raíz del proyecto,
# un paso arriba de backend/), también se busca ahí. load_dotenv no sobreescribe variables
# ya definidas, así que si el primero ya cargó algo, este segundo intento no lo pisa.
load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))

app = Flask(__name__, static_folder=None)
app.secret_key = os.environ.get("SECRET_KEY", "dev-secret-cambiar-en-produccion")

# Acceso al dashboard de administrador: credenciales separadas del login normal de
# prestamistas, configuradas por variables de entorno (nunca en texto plano en el código).
# El .env de cada instalación (local o del hosting) define su propio ADMIN_EMAIL y
# ADMIN_PASSWORD_HASH (generado con werkzeug.generate_password_hash).
ADMIN_EMAIL = (os.environ.get("ADMIN_EMAIL") or "").strip().lower()
ADMIN_PASSWORD_HASH = os.environ.get("ADMIN_PASSWORD_HASH") or ""


def login_requerido(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not session.get("usuario_id"):
            return jsonify({"error": "No autenticado"}), 401
        return f(*args, **kwargs)

    return wrapper


def admin_requerido(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        if not session.get("es_admin"):
            return jsonify({"error": "No autenticado como administrador"}), 401
        return f(*args, **kwargs)

    return wrapper


def _sin_cache(respuesta):
    """El HTML de estas páginas no debe quedar cacheado ni en el navegador ni en el
    hosting (LiteSpeed cache u otro proxy), para que cada actualización se vea de
    inmediato sin depender de que el usuario borre caché o pruebe en incógnito."""
    respuesta.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
    respuesta.headers["Pragma"] = "no-cache"
    respuesta.headers["Expires"] = "0"
    return respuesta


def _guardar_documento_identidad(archivo, nombre_esperado=None, numero_documento_esperado=None):
    """Guarda la foto o PDF del documento de identidad adjuntado para firmar, y si se
    conoce el nombre/numero de documento esperado intenta validar (best-effort, ver
    validacion_ocr.py) que el contenido corresponda a esa persona. Devuelve (nombre_archivo,
    None) si quedó guardado y validado (o si no se pudo validar), o (None, mensaje_error) si
    el archivo no es válido o claramente no coincide."""
    if not archivo or not archivo.filename:
        return None, "Debes adjuntar una foto o PDF de tu documento de identidad para poder firmar"

    extension = os.path.splitext(archivo.filename)[1].lower()
    if extension not in EXTENSIONES_DOCUMENTO_VALIDAS:
        return None, "El documento de identidad debe ser un archivo JPG o PDF"

    archivo.seek(0, os.SEEK_END)
    tamano = archivo.tell()
    archivo.seek(0)
    if tamano > TAMANO_MAXIMO_DOCUMENTO:
        return None, "El archivo del documento de identidad no puede superar 8 MB"

    nombre_archivo = f"{uuid.uuid4().hex}{extension}"
    ruta_absoluta = os.path.join(UPLOADS_DIR, nombre_archivo)
    archivo.save(ruta_absoluta)

    if nombre_esperado or numero_documento_esperado:
        ok, mensaje_error = validar_documento_identidad(ruta_absoluta, extension, nombre_esperado, numero_documento_esperado)
        if not ok:
            os.remove(ruta_absoluta)
            return None, mensaje_error

    return nombre_archivo, None


def _guardar_firma_dibujada(archivo):
    """Guarda la firma dibujada a mano (PNG generado desde un canvas en el navegador).
    Es obligatoria para poder firmar, igual que el documento de identidad."""
    if not archivo or not archivo.filename:
        return None, "Debes dibujar tu firma para poder firmar"
    nombre_archivo = f"{uuid.uuid4().hex}_firma.png"
    archivo.save(os.path.join(UPLOADS_DIR, nombre_archivo))
    return nombre_archivo, None


@app.route("/")
def index():
    return _sin_cache(make_response(send_from_directory(FRONTEND_DIR, "index.html")))


@app.route("/firmar/<token>")
def pagina_firma(token):
    return _sin_cache(make_response(send_from_directory(FRONTEND_DIR, "firmar.html")))


@app.route("/terminos")
def pagina_terminos():
    return _sin_cache(make_response(send_from_directory(FRONTEND_DIR, "terminos.html")))


@app.route("/educacion")
def pagina_educacion():
    return _sin_cache(make_response(send_from_directory(FRONTEND_DIR, "educacion.html")))


@app.route("/calculadora")
def pagina_calculadora():
    return _sin_cache(make_response(send_from_directory(FRONTEND_DIR, "calculadora.html")))


@app.route("/admin")
def pagina_admin():
    return _sin_cache(make_response(send_from_directory(FRONTEND_DIR, "admin.html")))


@app.route("/<path:filename>")
def frontend_static(filename):
    """Sirve app.js, style.css, firmar.js, etc. Igual que las páginas HTML, estos archivos
    también deben viajar sin caché: aunque el HTML referencia cada uno con ?v=<version> para
    forzar una URL distinta por deploy, un proxy de caché del hosting (ej. LiteSpeed) puede
    cachear por ruta de archivo sin importar el query string, sirviendo una versión vieja
    aunque el HTML ya pida la nueva."""
    return _sin_cache(make_response(send_from_directory(FRONTEND_DIR, filename)))


# ---------- Autenticación ----------

@app.route("/api/auth/registro", methods=["POST"])
def api_registro():
    data = request.get_json(force=True)
    nombre = (data.get("nombre") or "").strip()
    email = (data.get("email") or "").strip().lower()
    password = data.get("password") or ""
    telefono = (data.get("telefono") or "").strip()
    tipo_documento = data.get("tipo_documento")
    numero_documento = (data.get("numero_documento") or "").strip()
    direccion = (data.get("direccion") or "").strip()
    acepto_terminos = bool(data.get("acepto_terminos"))

    if not nombre or not email or len(password) < 6:
        return jsonify({"error": "Nombre, email y password (min. 6 caracteres) son requeridos"}), 400

    if not telefono or not numero_documento or not direccion:
        return jsonify({"error": "Teléfono, documento y dirección son requeridos para poder identificarte como prestamista en los contratos"}), 400

    if tipo_documento not in TIPOS_DOCUMENTO_VALIDOS:
        return jsonify({"error": "Tipo de documento inválido"}), 400

    if not acepto_terminos:
        return jsonify({"error": "Debes aceptar los términos y el tratamiento de datos personales"}), 400

    if db.obtener_usuario_por_email(email):
        return jsonify({"error": "Ya existe una cuenta con ese email"}), 409

    usuario_id = db.crear_usuario(
        nombre, email, generate_password_hash(password), telefono, acepto_terminos,
        tipo_documento, numero_documento, direccion,
    )
    session["usuario_id"] = usuario_id
    return jsonify({"id": usuario_id, "nombre": nombre, "email": email})


@app.route("/api/auth/login", methods=["POST"])
def api_login():
    data = request.get_json(force=True)
    email = (data.get("email") or "").strip().lower()
    password = data.get("password") or ""

    usuario = db.obtener_usuario_por_email(email)
    if not usuario or not check_password_hash(usuario["password_hash"], password):
        return jsonify({"error": "Email o password incorrectos"}), 401

    session["usuario_id"] = usuario["id"]
    return jsonify({"id": usuario["id"], "nombre": usuario["nombre"], "email": usuario["email"]})


@app.route("/api/auth/logout", methods=["POST"])
def api_logout():
    session.clear()
    return jsonify({"ok": True})


@app.route("/api/auth/me", methods=["GET"])
def api_me():
    usuario_id = session.get("usuario_id")
    if not usuario_id:
        return jsonify({"usuario": None})
    usuario = db.obtener_usuario(usuario_id)
    if not usuario:
        session.clear()
        return jsonify({"usuario": None})
    return jsonify(
        {
            "usuario": {
                "id": usuario["id"],
                "nombre": usuario["nombre"],
                "email": usuario["email"],
                "telefono": usuario.get("telefono"),
                "direccion": usuario.get("direccion"),
                "tipo_documento": usuario.get("tipo_documento"),
                "numero_documento": usuario.get("numero_documento"),
            }
        }
    )


@app.route("/api/auth/perfil", methods=["PUT"])
@login_requerido
def api_actualizar_perfil():
    """El prestamista puede editar sus propios datos de contacto, pero NO su tipo/número de
    documento: esa identidad queda fija una vez creada la cuenta (si necesita corregirla,
    debe solicitarlo directamente al equipo de PrestaRed) para no debilitar la trazabilidad
    de los contratos ya firmados con esos datos."""
    data = request.get_json(force=True)
    nombre = (data.get("nombre") or "").strip()
    email = (data.get("email") or "").strip().lower()
    telefono = (data.get("telefono") or "").strip()
    direccion = (data.get("direccion") or "").strip()

    if not nombre or not email:
        return jsonify({"error": "Nombre y email son requeridos"}), 400

    existente = db.obtener_usuario_por_email(email)
    if existente and existente["id"] != session["usuario_id"]:
        return jsonify({"error": "Ya existe otra cuenta con ese email"}), 409

    db.actualizar_perfil_usuario(session["usuario_id"], nombre, email, telefono, direccion)
    usuario = db.obtener_usuario(session["usuario_id"])
    return jsonify(
        {
            "id": usuario["id"],
            "nombre": usuario["nombre"],
            "email": usuario["email"],
            "telefono": usuario.get("telefono"),
            "direccion": usuario.get("direccion"),
            "tipo_documento": usuario.get("tipo_documento"),
            "numero_documento": usuario.get("numero_documento"),
        }
    )


# ---------- Préstamos ----------

@app.route("/api/prestamos", methods=["GET"])
@login_requerido
def api_listar_prestamos():
    return jsonify(db.listar_prestamos(session["usuario_id"]))


@app.route("/api/prestamos/buscar_documento", methods=["GET"])
@login_requerido
def api_buscar_documento():
    """Permite que el prestamista, mientras llena el formulario de nuevo préstamo, sepa si
    ese número de documento ya existe en la plataforma (con cualquier prestamista) y vea el
    nombre y el credit score de esa persona. Por privacidad, esto NO expone teléfono, dirección
    ni correo de esa persona: esos datos los completa el deudor mismo al momento de firmar."""
    numero_documento = (request.args.get("numero_documento") or "").strip()
    if not numero_documento:
        return jsonify({"existe": False})
    prestamos = db.historial_prestamos_por_documento(numero_documento)
    if not prestamos:
        return jsonify({"existe": False})
    return jsonify(
        {
            "existe": True,
            "nombre": prestamos[-1]["prestatario_nombre"],
            "credit_score": calcular_credit_score(prestamos),
        }
    )


@app.route("/api/prestamos", methods=["POST"])
@login_requerido
def api_crear_prestamo():
    data = request.get_json(force=True)
    # Teléfono, dirección y email del deudor son opcionales: si su documento ya existe en la
    # plataforma (ver /api/prestamos/buscar_documento), por privacidad esos datos no se piden
    # al prestamista, sino que el deudor los completa el mismo al firmar el acuerdo.
    campos_requeridos = [
        "ciudad", "prestatario_nombre", "prestatario_numero_documento", "monto", "fecha_inicio",
    ]
    faltantes = [c for c in campos_requeridos if not data.get(c)]
    if faltantes:
        return jsonify({"error": f"Faltan campos: {', '.join(faltantes)}"}), 400

    moneda = data.get("moneda") or "COP"
    tipo_tasa = data.get("tipo_tasa") or "mes_vencido"
    tipo_periodo = data.get("tipo_periodo") or "mes"
    prestatario_tipo_documento = data.get("prestatario_tipo_documento")
    if moneda not in MONEDAS_VALIDAS:
        return jsonify({"error": "Moneda inválida"}), 400
    if tipo_tasa not in TIPOS_TASA_VALIDOS:
        return jsonify({"error": "Tipo de tasa inválido"}), 400
    if tipo_periodo not in TIPOS_PERIODO_VALIDOS:
        return jsonify({"error": "Tipo de período inválido"}), 400
    if prestatario_tipo_documento not in TIPOS_DOCUMENTO_VALIDOS:
        return jsonify({"error": "Tipo de documento del deudor inválido"}), 400

    codeudor_nombre = (data.get("codeudor_nombre") or "").strip() or None
    codeudor_tipo_documento = data.get("codeudor_tipo_documento")
    codeudor_numero_documento = (data.get("codeudor_numero_documento") or "").strip()
    codeudor_direccion = (data.get("codeudor_direccion") or "").strip()
    codeudor_telefono = (data.get("codeudor_telefono") or "").strip()
    if codeudor_nombre:
        if not codeudor_numero_documento:
            return jsonify({"error": "Si hay codeudor, su número de documento es requerido"}), 400
        if codeudor_tipo_documento not in TIPOS_DOCUMENTO_VALIDOS:
            return jsonify({"error": "Tipo de documento del codeudor inválido"}), 400

    try:
        monto = float(data["monto"])
        tasa_interes = float(data.get("tasa_interes", 0))
        tasa_interes_mora = float(data.get("tasa_interes_mora", 0))
        numero_cuotas = int(data.get("numero_cuotas", 1))
    except (TypeError, ValueError):
        return jsonify({"error": "monto, tasa_interes, tasa_interes_mora y numero_cuotas deben ser numéricos"}), 400

    usura = validar_usura(tasa_interes, tipo_tasa, tasa_interes_mora)
    excede = usura["excede_interes"] or usura["excede_mora"]
    if excede and not data.get("confirmar_riesgo_tasa"):
        return jsonify({"error": "tasa_excede_usura", "usura": usura}), 409

    try:
        fecha_vencimiento = calcular_fecha_vencimiento(data["fecha_inicio"], numero_cuotas, tipo_periodo)
        cuotas = calcular_cuotas(monto, tasa_interes, tipo_tasa, data["fecha_inicio"], numero_cuotas, tipo_periodo)
    except ValueError as e:
        return jsonify({"error": str(e)}), 400

    clausulas = sanitizar_clausulas(data.get("clausulas"))

    servicio_seguro = bool(data.get("servicio_seguro"))
    servicio_whatsapp = bool(data.get("servicio_whatsapp"))
    costo_seguro = round(monto * (COSTO_SEGURO_PCT / 100), 2) if servicio_seguro else 0
    costo_whatsapp = COSTO_WHATSAPP_COP if servicio_whatsapp else 0

    prestamo_id = db.crear_prestamo(
        {
            "prestamista_id": session["usuario_id"],
            "ciudad": data["ciudad"],
            "prestatario_nombre": data["prestatario_nombre"],
            "prestatario_telefono": data.get("prestatario_telefono"),
            "prestatario_email": data.get("prestatario_email"),
            "prestatario_tipo_documento": prestatario_tipo_documento,
            "prestatario_numero_documento": data.get("prestatario_numero_documento"),
            "prestatario_direccion": data.get("prestatario_direccion"),
            "monto": monto,
            "moneda": moneda,
            "tasa_interes": tasa_interes,
            "tipo_tasa": tipo_tasa,
            "tasa_interes_mora": tasa_interes_mora,
            "fecha_inicio": data["fecha_inicio"],
            "fecha_vencimiento": fecha_vencimiento,
            "numero_cuotas": numero_cuotas,
            "tipo_periodo": tipo_periodo,
            "notas": data.get("notas"),
            "tasa_excede_usura": excede,
            "clausulas": clausulas,
            "codeudor_nombre": codeudor_nombre,
            "codeudor_telefono": codeudor_telefono or None,
            "codeudor_email": data.get("codeudor_email"),
            "codeudor_tipo_documento": codeudor_tipo_documento if codeudor_nombre else None,
            "codeudor_numero_documento": codeudor_numero_documento or None,
            "codeudor_direccion": codeudor_direccion or None,
            "servicio_seguro": servicio_seguro,
            "costo_seguro": costo_seguro,
            "servicio_whatsapp": servicio_whatsapp,
            "costo_whatsapp": costo_whatsapp,
        },
        cuotas,
    )
    return jsonify(db.obtener_prestamo(prestamo_id, session["usuario_id"]))


@app.route("/api/config/tasa_referencia_mercado", methods=["GET"])
def api_tasa_referencia_mercado():
    """Nota puramente informativa y sin datos sensibles: se deja publica (sin login) para
    que tambien la pueda usar la calculadora de cuota, que no requiere cuenta."""
    return jsonify(tasa_referencia_mercado())


@app.route("/api/calculadora/simular", methods=["POST"])
def api_calculadora_simular():
    """Calculadora publica de cuota (sin necesidad de cuenta): usa la misma formula de
    amortizacion a cuota fija que el resto de la app, fijando tasa mensual y cuotas
    mensuales (el caso mas comun) para mantener el formulario simple. La fecha de inicio es
    irrelevante para el valor de la cuota (solo afecta las fechas de cada cuota, que aqui no
    se muestran), asi que se usa una fecha fija cualquiera."""
    data = request.get_json(force=True)
    try:
        monto = float(data.get("monto", 0))
        tasa_mensual = float(data.get("tasa_mensual", 0))
        numero_cuotas = int(data.get("numero_cuotas", 0))
    except (TypeError, ValueError):
        return jsonify({"error": "monto, tasa_mensual y numero_cuotas deben ser numéricos"}), 400
    if monto <= 0 or numero_cuotas <= 0:
        return jsonify({"error": "El monto y el número de cuotas deben ser mayores a cero"}), 400
    if tasa_mensual < 0:
        return jsonify({"error": "La tasa no puede ser negativa"}), 400

    cuotas = calcular_cuotas(monto, tasa_mensual, "mes_vencido", "2000-01-01", numero_cuotas, "mes")
    monto_total = round(sum(c["monto"] for c in cuotas), 2)
    return jsonify({
        "valor_cuota": cuotas[0]["monto"],
        "monto_total": monto_total,
        "interes_total": round(monto_total - monto, 2),
    })


@app.route("/api/usura/validar", methods=["POST"])
@login_requerido
def api_validar_usura():
    data = request.get_json(force=True)
    tipo_tasa = data.get("tipo_tasa") or "mes_vencido"
    if tipo_tasa not in TIPOS_TASA_VALIDOS:
        return jsonify({"error": "Tipo de tasa inválido"}), 400
    try:
        tasa_interes = float(data.get("tasa_interes", 0))
        tasa_interes_mora = float(data.get("tasa_interes_mora", 0))
    except (TypeError, ValueError):
        return jsonify({"error": "tasa_interes y tasa_interes_mora deben ser numéricos"}), 400
    return jsonify(validar_usura(tasa_interes, tipo_tasa, tasa_interes_mora))


@app.route("/api/prestamos/fecha_vencimiento", methods=["POST"])
@login_requerido
def api_calcular_fecha_vencimiento():
    """Ademas de la fecha de vencimiento, si ya vienen monto y tasa_interes tambien
    devuelve una simulacion del valor de cada cuota y el interes total: le permite al
    prestamista ajustar el plazo o la tasa antes de guardar el prestamo, para asegurarse
    de que el deudor tenga capacidad de pago."""
    data = request.get_json(force=True)
    tipo_periodo = data.get("tipo_periodo") or "mes"
    tipo_tasa = data.get("tipo_tasa") or "mes_vencido"
    if tipo_periodo not in TIPOS_PERIODO_VALIDOS:
        return jsonify({"error": "Tipo de período inválido"}), 400
    if tipo_tasa not in TIPOS_TASA_VALIDOS:
        return jsonify({"error": "Tipo de tasa inválido"}), 400
    if not data.get("fecha_inicio"):
        return jsonify({"error": "Falta fecha_inicio"}), 400
    try:
        numero_cuotas = int(data.get("numero_cuotas", 1))
    except (TypeError, ValueError):
        return jsonify({"error": "numero_cuotas debe ser numérico"}), 400

    try:
        resultado = {
            "fecha_vencimiento": calcular_fecha_vencimiento(data["fecha_inicio"], numero_cuotas, tipo_periodo)
        }

        monto = data.get("monto")
        if monto:
            monto = float(monto)
            tasa_interes = float(data.get("tasa_interes", 0))
            if monto > 0:
                cuotas = calcular_cuotas(monto, tasa_interes, tipo_tasa, data["fecha_inicio"], numero_cuotas, tipo_periodo)
                monto_total = sum(c["monto"] for c in cuotas)
                resultado["valor_cuota"] = cuotas[0]["monto"]
                resultado["monto_total"] = round(monto_total, 2)
                resultado["interes_total"] = round(monto_total - monto, 2)
    except (TypeError, ValueError) as e:
        return jsonify({"error": str(e) or "Datos numéricos inválidos"}), 400

    return jsonify(resultado)


@app.route("/api/prestamos/<int:prestamo_id>", methods=["GET"])
@login_requerido
def api_obtener_prestamo(prestamo_id):
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404
    return jsonify(prestamo)


@app.route("/api/prestamos/<int:prestamo_id>", methods=["DELETE"])
@login_requerido
def api_eliminar_prestamo(prestamo_id):
    db.eliminar_prestamo(prestamo_id, session["usuario_id"])
    return jsonify({"ok": True})


@app.route("/api/prestamos/<int:prestamo_id>/resumen", methods=["GET"])
@login_requerido
def api_resumen_deuda(prestamo_id):
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404
    fecha = request.args.get("fecha")
    return jsonify(calcular_resumen_deuda(prestamo, fecha))


@app.route("/api/prestamos/<int:prestamo_id>/cuotas/<int:cuota_id>/pagar", methods=["POST"])
@login_requerido
def api_marcar_cuota_pagada(prestamo_id, cuota_id):
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404

    data = request.get_json(silent=True) or {}
    pagada = data.get("pagada", True)

    interes_mora = 0
    if pagada:
        resumen = calcular_resumen_deuda(prestamo)
        cuota_resumen = next((c for c in resumen["cuotas_pendientes"] if c["cuota_id"] == cuota_id), None)
        interes_mora = cuota_resumen["interes_mora"] if cuota_resumen else 0

    db.marcar_cuota_pagada(cuota_id, pagada, interes_mora)

    if db.contar_cuotas_pendientes(prestamo_id) == 0:
        db.actualizar_estado_prestamo(prestamo_id, "pagado")
    elif prestamo["estado"] == "pagado":
        db.actualizar_estado_prestamo(prestamo_id, "activo")

    return jsonify(db.obtener_prestamo(prestamo_id, session["usuario_id"]))


@app.route("/api/prestamos/<int:prestamo_id>/pagar_todo", methods=["POST"])
@login_requerido
def api_pagar_todo(prestamo_id):
    """Pago total anticipado: el deudor salda hoy el préstamo completo, pagando el capital
    más el interés ya causado según el tiempo transcurrido (ver calcular_saldo_total), sin
    cobrar el interés de los períodos que todavía no han pasado. Todas las cuotas pendientes
    quedan marcadas como pagadas."""
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404

    for cuota in prestamo["cuotas"]:
        if not cuota["pagada"]:
            db.marcar_cuota_pagada(cuota["id"], True, 0)

    db.actualizar_estado_prestamo(prestamo_id, "pagado")
    return jsonify(db.obtener_prestamo(prestamo_id, session["usuario_id"]))


@app.route("/api/prestamos/<int:prestamo_id>/estado", methods=["PUT"])
@login_requerido
def api_actualizar_estado(prestamo_id):
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404
    data = request.get_json(force=True)
    estado = data.get("estado")
    if estado not in ("activo", "pagado", "mora", "cancelado"):
        return jsonify({"error": "Estado inválido"}), 400
    db.actualizar_estado_prestamo(prestamo_id, estado)
    return jsonify(db.obtener_prestamo(prestamo_id, session["usuario_id"]))


@app.route("/api/prestamos/<int:prestamo_id>/firmar", methods=["POST"])
@login_requerido
def api_firmar_prestamista(prestamo_id):
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404

    prestamista = db.obtener_usuario(session["usuario_id"])
    nombre_archivo, error = _guardar_documento_identidad(
        request.files.get("documento"),
        nombre_esperado=prestamista.get("nombre"),
        numero_documento_esperado=prestamista.get("numero_documento"),
    )
    if error:
        return jsonify({"error": error}), 400

    nombre_firma_dibujada, error = _guardar_firma_dibujada(request.files.get("firma_dibujada"))
    if error:
        return jsonify({"error": error}), 400

    firma_id = db.crear_firma(
        prestamo_id, "prestamista", prestamista["nombre"], request.remote_addr, hash_acuerdo(prestamo),
        nombre_archivo, nombre_firma_dibujada,
    )
    if firma_id is None:
        return jsonify({"error": "Ya firmaste este préstamo"}), 409

    return jsonify(db.obtener_prestamo(prestamo_id, session["usuario_id"]))


# ---------- Firma pública (deudor, sin login) ----------

@app.route("/api/publico/prestamos/<token>", methods=["GET"])
def api_publico_obtener_prestamo(token):
    prestamo = db.obtener_prestamo_por_token(token)
    if not prestamo:
        return jsonify({"error": "Enlace inválido"}), 404

    prestamista = db.obtener_usuario(prestamo["prestamista_id"])
    return jsonify(
        {
            "prestamista_nombre": prestamista["nombre"],
            "prestatario_nombre": prestamo["prestatario_nombre"],
            "prestatario_telefono": prestamo.get("prestatario_telefono"),
            "prestatario_direccion": prestamo.get("prestatario_direccion"),
            "prestatario_email": prestamo.get("prestatario_email"),
            "codeudor_nombre": prestamo.get("codeudor_nombre"),
            "codeudor_telefono": prestamo.get("codeudor_telefono"),
            "codeudor_direccion": prestamo.get("codeudor_direccion"),
            "codeudor_email": prestamo.get("codeudor_email"),
            "monto": prestamo["monto"],
            "moneda": prestamo.get("moneda") or "COP",
            "tasa_interes": prestamo["tasa_interes"],
            "tipo_tasa": prestamo.get("tipo_tasa") or "mes_vencido",
            "tasa_interes_mora": prestamo["tasa_interes_mora"],
            "fecha_inicio": prestamo["fecha_inicio"],
            "fecha_vencimiento": prestamo["fecha_vencimiento"],
            "numero_cuotas": prestamo["numero_cuotas"],
            "cuotas": [
                {"numero": c["numero"], "fecha_vencimiento": c["fecha_vencimiento"], "monto": c["monto"]}
                for c in prestamo["cuotas"]
            ],
            "firmas": [{"rol": f["rol"], "nombre_firmante": f["nombre_firmante"], "fecha_firma": f["fecha_firma"]} for f in prestamo["firmas"]],
        }
    )


@app.route("/api/publico/prestamos/<token>/firmar", methods=["POST"])
def api_publico_firmar(token):
    prestamo = db.obtener_prestamo_por_token(token)
    if not prestamo:
        return jsonify({"error": "Enlace inválido"}), 404

    data = request.form
    nombre = (data.get("nombre") or "").strip()
    rol = data.get("rol") or "prestatario"
    if rol not in ("prestatario", "codeudor"):
        return jsonify({"error": "Rol inválido"}), 400
    if rol == "codeudor" and not prestamo.get("codeudor_nombre"):
        return jsonify({"error": "Este préstamo no tiene codeudor configurado"}), 400
    if not nombre:
        return jsonify({"error": "Debes escribir tu nombre completo para firmar"}), 400

    if rol == "codeudor":
        nombre_esperado = prestamo.get("codeudor_nombre")
        numero_documento_esperado = prestamo.get("codeudor_numero_documento")
    else:
        nombre_esperado = prestamo.get("prestatario_nombre")
        numero_documento_esperado = prestamo.get("prestatario_numero_documento")

    nombre_archivo, error = _guardar_documento_identidad(
        request.files.get("documento"),
        nombre_esperado=nombre_esperado,
        numero_documento_esperado=numero_documento_esperado,
    )
    if error:
        return jsonify({"error": error}), 400

    nombre_firma_dibujada, error = _guardar_firma_dibujada(request.files.get("firma_dibujada"))
    if error:
        return jsonify({"error": error}), 400

    firma_id = db.crear_firma(
        prestamo["id"], rol, nombre, request.remote_addr, hash_acuerdo(prestamo),
        nombre_archivo, nombre_firma_dibujada,
    )
    if firma_id is None:
        return jsonify({"error": f"Este préstamo ya fue firmado como {rol}"}), 409

    ingreso_mensual = None
    gastos_mensuales = None
    if rol == "prestatario":
        try:
            if data.get("ingreso_mensual"):
                ingreso_mensual = max(0, float(data["ingreso_mensual"]))
            if data.get("gastos_mensuales"):
                gastos_mensuales = max(0, float(data["gastos_mensuales"]))
        except (TypeError, ValueError):
            return jsonify({"error": "El ingreso y los gastos mensuales deben ser numéricos"}), 400

    db.completar_datos_persona(
        prestamo["id"], rol,
        telefono=(data.get("telefono") or "").strip() or None,
        direccion=(data.get("direccion") or "").strip() or None,
        email=(data.get("email") or "").strip() or None,
        ingreso_mensual=ingreso_mensual,
        gastos_mensuales=gastos_mensuales,
    )

    return jsonify({"ok": True})


@app.route("/api/publico/prestamos/<token>/acuerdo.pdf", methods=["GET"])
def api_publico_acuerdo_pdf(token):
    prestamo = db.obtener_prestamo_por_token(token)
    if not prestamo:
        return jsonify({"error": "Enlace inválido"}), 404
    prestamista = db.obtener_usuario(prestamo["prestamista_id"])
    pdf_bytes = generar_acuerdo_pdf(prestamo, prestamista, UPLOADS_DIR)
    return send_file(
        __import__("io").BytesIO(pdf_bytes),
        mimetype="application/pdf",
        as_attachment=not request.args.get("inline"),
        download_name=f"acuerdo_prestamo_{prestamo['id']}.pdf",
    )


@app.route("/api/prestamos/<int:prestamo_id>/acuerdo.pdf", methods=["GET"])
@login_requerido
def api_acuerdo_pdf(prestamo_id):
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404
    prestamista = db.obtener_usuario(session["usuario_id"])
    pdf_bytes = generar_acuerdo_pdf(prestamo, prestamista, UPLOADS_DIR)
    return send_file(
        __import__("io").BytesIO(pdf_bytes),
        mimetype="application/pdf",
        as_attachment=not request.args.get("inline"),
        download_name=f"acuerdo_prestamo_{prestamo_id}.pdf",
    )


@app.route("/api/publico/prestamos/<token>/pagare.pdf", methods=["GET"])
def api_publico_pagare_pdf(token):
    prestamo = db.obtener_prestamo_por_token(token)
    if not prestamo:
        return jsonify({"error": "Enlace inválido"}), 404
    prestamista = db.obtener_usuario(prestamo["prestamista_id"])
    pdf_bytes = generar_pagare_pdf(prestamo, prestamista, UPLOADS_DIR)
    return send_file(
        __import__("io").BytesIO(pdf_bytes),
        mimetype="application/pdf",
        as_attachment=not request.args.get("inline"),
        download_name=f"pagare_prestamo_{prestamo['id']}.pdf",
    )


@app.route("/api/prestamos/<int:prestamo_id>/pagare.pdf", methods=["GET"])
@login_requerido
def api_pagare_pdf(prestamo_id):
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404
    prestamista = db.obtener_usuario(session["usuario_id"])
    pdf_bytes = generar_pagare_pdf(prestamo, prestamista, UPLOADS_DIR)
    return send_file(
        __import__("io").BytesIO(pdf_bytes),
        mimetype="application/pdf",
        as_attachment=not request.args.get("inline"),
        download_name=f"pagare_prestamo_{prestamo_id}.pdf",
    )


@app.route("/api/prestamos/<int:prestamo_id>/enviar_firma", methods=["POST"])
@login_requerido
def api_enviar_firma(prestamo_id):
    """Envia el enlace de firma al deudor (y al codeudor, si hay) por correo, cuando el
    SMTP esta configurado en este servidor. Siempre devuelve, ademas, el enlace y el
    telefono de cada uno para que el frontend pueda ofrecer un enlace de WhatsApp
    (wa.me) que el prestamista envia el mismo con un clic, sin costo ni integracion."""
    prestamo = db.obtener_prestamo(prestamo_id, session["usuario_id"])
    if not prestamo:
        return jsonify({"error": "No encontrado"}), 404

    prestamista = db.obtener_usuario(session["usuario_id"])
    enlace = f"{request.host_url.rstrip('/')}/firmar/{prestamo['token_publico']}"
    cuerpo_html = (
        f"<p>Hola {{nombre}},</p>"
        f"<p><b>{prestamista['nombre']}</b> te registró un préstamo en PrestaRed. "
        f"Para revisarlo y firmarlo electrónicamente, entra a este enlace:</p>"
        f"<p><a href=\"{enlace}\">{enlace}</a></p>"
        f"<p>Este enlace es personal, no lo compartas con nadie más.</p>"
    )

    destinatarios = []

    email_enviado = enviar_correo(
        prestamo.get("prestatario_email"),
        "Tienes un préstamo para firmar en PrestaRed",
        cuerpo_html.format(nombre=prestamo["prestatario_nombre"]),
    )
    destinatarios.append(
        {
            "rol": "prestatario",
            "nombre": prestamo["prestatario_nombre"],
            "telefono": prestamo.get("prestatario_telefono"),
            "email": prestamo.get("prestatario_email"),
            "email_enviado": email_enviado,
        }
    )

    if prestamo.get("codeudor_nombre"):
        email_enviado_codeudor = enviar_correo(
            prestamo.get("codeudor_email"),
            "Te agregaron como codeudor en un préstamo de PrestaRed",
            cuerpo_html.format(nombre=prestamo["codeudor_nombre"]),
        )
        destinatarios.append(
            {
                "rol": "codeudor",
                "nombre": prestamo["codeudor_nombre"],
                "telefono": prestamo.get("codeudor_telefono"),
                "email": prestamo.get("codeudor_email"),
                "email_enviado": email_enviado_codeudor,
            }
        )

    return jsonify({"enlace": enlace, "smtp_configurado": smtp_configurado(), "destinatarios": destinatarios})


# ---------- Dashboard de administrador ----------

@app.route("/api/admin/login", methods=["POST"])
def api_admin_login():
    data = request.get_json(force=True)
    email = (data.get("email") or "").strip().lower()
    password = data.get("password") or ""

    if not ADMIN_EMAIL or not ADMIN_PASSWORD_HASH:
        return jsonify({"error": "El acceso de administrador no está configurado en este servidor"}), 500

    if email != ADMIN_EMAIL or not check_password_hash(ADMIN_PASSWORD_HASH, password):
        return jsonify({"error": "Email o password incorrectos"}), 401

    session["es_admin"] = True
    return jsonify({"ok": True, "email": email})


@app.route("/api/admin/logout", methods=["POST"])
def api_admin_logout():
    session.pop("es_admin", None)
    return jsonify({"ok": True})


@app.route("/api/admin/me", methods=["GET"])
def api_admin_me():
    return jsonify({"es_admin": bool(session.get("es_admin"))})


@app.route("/api/admin/estadisticas", methods=["GET"])
@admin_requerido
def api_admin_estadisticas():
    periodo = request.args.get("periodo") or "todo"
    if periodo not in PERIODOS_DASHBOARD:
        return jsonify({"error": "Período inválido"}), 400

    prestamos = db.listar_todos_los_prestamos_completos()
    estadisticas = calcular_estadisticas_admin(prestamos, periodo)
    estadisticas["usuarios_totales"] = db.contar_usuarios_totales()
    estadisticas["usuarios_nuevos_periodo"] = (
        db.contar_usuarios_nuevos_desde(estadisticas["desde"]) if estadisticas["desde"] else estadisticas["usuarios_totales"]
    )
    return jsonify(estadisticas)


@app.route("/api/admin/prestamistas", methods=["GET"])
@admin_requerido
def api_admin_listar_prestamistas():
    prestamistas = db.listar_prestamistas_admin()
    return jsonify(
        [
            {
                "id": u["id"],
                "nombre": u["nombre"],
                "email": u["email"],
                "telefono": u.get("telefono"),
                "creado_en": u["creado_en"],
                "numero_prestamos": u["numero_prestamos"],
            }
            for u in prestamistas
        ]
    )


@app.route("/api/admin/prestamistas/<int:usuario_id>", methods=["GET"])
@admin_requerido
def api_admin_detalle_prestamista(usuario_id):
    usuario = db.obtener_usuario(usuario_id)
    if not usuario:
        return jsonify({"error": "No encontrado"}), 404
    prestamos = db.listar_prestamos(usuario_id)
    prestamos_completos = [db.obtener_prestamo(p["id"], usuario_id) for p in prestamos]
    estadisticas = calcular_estadisticas_admin(prestamos_completos, "todo")
    return jsonify(
        {
            "usuario": {
                "id": usuario["id"],
                "nombre": usuario["nombre"],
                "email": usuario["email"],
                "telefono": usuario.get("telefono"),
                "tipo_documento": usuario.get("tipo_documento"),
                "numero_documento": usuario.get("numero_documento"),
                "direccion": usuario.get("direccion"),
                "creado_en": usuario["creado_en"],
            },
            "estadisticas": estadisticas,
            "prestamos": prestamos,
        }
    )


@app.route("/api/admin/deudores", methods=["GET"])
@admin_requerido
def api_admin_listar_deudores():
    return jsonify(db.listar_deudores_unicos())


@app.route("/api/admin/deudores/<numero_documento>", methods=["GET"])
@admin_requerido
def api_admin_detalle_deudor(numero_documento):
    prestamos = db.historial_prestamos_por_documento(numero_documento)
    if not prestamos:
        return jsonify({"error": "No encontrado"}), 404
    score = calcular_credit_score(prestamos)
    prestamistas_ids = {p["prestamista_id"] for p in prestamos}
    prestamistas = {pid: db.obtener_usuario(pid) for pid in prestamistas_ids}
    return jsonify(
        {
            "numero_documento": numero_documento,
            "nombre": prestamos[0]["prestatario_nombre"],
            "credit_score": score,
            "prestamos": [
                {
                    "id": p["id"],
                    "prestamista_nombre": prestamistas[p["prestamista_id"]]["nombre"] if prestamistas.get(p["prestamista_id"]) else "N/A",
                    "monto": p["monto"],
                    "moneda": p.get("moneda") or "COP",
                    "tasa_interes": p["tasa_interes"],
                    "tipo_tasa": p.get("tipo_tasa"),
                    "fecha_inicio": p["fecha_inicio"],
                    "fecha_vencimiento": p["fecha_vencimiento"],
                    "estado": p["estado"],
                    "creado_en": p["creado_en"],
                }
                for p in prestamos
            ],
        }
    )


def _abrir_navegador():
    webbrowser.open("http://localhost:5001")


if __name__ == "__main__":
    db.init_db()
    if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
        threading.Timer(1.5, _abrir_navegador).start()
    app.run(host="0.0.0.0", port=5001, debug=True)
