# === STANDARD LIBRARY ===
from functools import wraps
from top_horarios_multi import bp as top_horarios_multi_bp
from admin_service import admin_bp
import csv
import json
import os
import re
import secrets
import smtplib
import traceback
from collections import Counter
from datetime import date, datetime, time, timedelta, timezone
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formataddr
from zoneinfo import ZoneInfo
from decimal import Decimal

# === THIRD-PARTY LIBRARIES ===
import numpy as np
import pandas as pd
import requests
import stripe
from dotenv import load_dotenv

from flask import (
    Flask, abort, flash, jsonify, redirect, render_template,
    render_template_string, request, session, url_for
)
from markupsafe import Markup


from flask_babel import get_locale
from flask_babel import Babel, _, get_locale as flask_babel_get_locale
from flask_login import (
    LoginManager, login_user, logout_user,
    login_required, current_user
)
from flask_mail import Mail, Message
from itsdangerous import URLSafeTimedSerializer
from openai import OpenAI
from werkzeug.security import generate_password_hash, check_password_hash

# === LOCAL MODULES / BLUEPRINTS ===
from utils.schwab_client import get_schwab_client
from models import db, User, Youtuber
from ccitm import ccitm_bp
from neutralEdge import neutralEdge_bp
from backtestingIdea import backtestingIdea_bp
from flask_wtf.csrf import CSRFProtect, generate_csrf


# === Load Environment Variables ===
dotenv_path = "/var/www/html/backtestingmarket/.env"
load_dotenv(dotenv_path=dotenv_path)


# === Flask App Initialization ===
app = Flask(__name__)



# === Core Security / Secrets ===
app.secret_key = os.getenv("FLASK_SECRET_KEY")
if not app.secret_key:
    raise RuntimeError("FLASK_SECRET_KEY no está definida en el entorno.")

# === App-wide Configuration ===
serializer = URLSafeTimedSerializer(app.secret_key)
LOG_FILE_PATH = "/var/www/html/flask_project/logs/login_logs.csv"

# === Login Manager Setup ===
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"

# 🔥 Desactivar el mensaje automático
login_manager.login_message = None
login_manager.login_message_category = None

@login_manager.user_loader
def load_user(user_id: str):
    """Carga el usuario actual por ID (requerido por Flask-Login)."""
    # return User.query.get(int(user_id))
    return db.session.get(User, int(user_id))


# === Blueprints Registration ===
app.register_blueprint(ccitm_bp, url_prefix="/ccitm")
app.register_blueprint(neutralEdge_bp, url_prefix="/neutralEdge")
app.register_blueprint(backtestingIdea_bp, url_prefix="/backtestingIdea")

app.register_blueprint(admin_bp)

app.register_blueprint(top_horarios_multi_bp)


# === CSRF Protection ===
csrf = CSRFProtect(app)

# Exenta todo el blueprint backtestingIdea
csrf.exempt(backtestingIdea_bp)
csrf.exempt(admin_bp)

# Inyectar csrf_token en los templates Jinja


@app.context_processor
def inject_csrf_token():
    return dict(csrf_token=generate_csrf)


# === Babel Configuration ===
app.config["BABEL_DEFAULT_LOCALE"] = "en"
app.config["BABEL_TRANSLATION_DIRECTORIES"] = "translations"

# === Locale Selection (per request) ===


def seleccionar_idioma():
    """
    Determina el idioma preferido:
    1) Si hay 'language' en sesión, úsalo.
    2) Si no, usa el mejor match del header Accept-Language entre ['es', 'en'].
    3) Fallback: 'es'.
    """
    idioma = session.get("language")
    if idioma:
        app.logger.debug(f"Idioma en sesión: {idioma}")
        return idioma

    best = request.accept_languages.best_match(["es", "en"])
    app.logger.debug(f"Idioma detectado: {best}")
    return best or "es"


# === Babel Initialization ===
babel = Babel(app, locale_selector=seleccionar_idioma)


# === Database (SQLAlchemy) Configuration ===
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////var/www/html/backtestingmarket/instance/users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)
with app.app_context():
    db.create_all()

# === Session Configuration ===
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=60)

# === Predictor Data Path ===
PATH_UBUNTU = "/var/www/html/backtestingmarket/predictor_data/data/"

# === Language Routes ===


@app.route("/set_language/<lang>")
def set_language(lang):
    session['language'] = lang
    return redirect(request.referrer or url_for('home'))

# === Babel Initialization ===
# (Ya fue configurado arriba con `seleccionar_idioma`; evitamos redefinirlo aquí.)

# === Template Context: Locale Injection ===


@app.context_processor
def inject_locale():
    # Devolvemos string para evitar problemas al serializar/renderizar.
    return {"locale_actual": str(flask_babel_get_locale() or "en")}


# @app.route("/")
# def home():
#     return render_template("under_construction.html")


@app.route("/")
def home():
    return render_template("home.html")



@app.route("/about")
def about():
    return render_template("about.html")


@app.route("/services")
def services():
    return render_template("services.html")

@app.route("/features")
def features():
    return render_template("features.html")



@app.route("/disclaimer")
def disclaimer():
    return render_template("disclaimer.html")


@app.route('/terms')
def terms():
    return render_template('terms.html')


@app.route('/settings', methods=['POST'])
@login_required
def user_settings():
    try:
        current_user.contracts_per_trade = int(request.form.get('contracts_per_trade', 1))
        current_user.commission_per_leg = float(request.form.get('commission_per_leg', 0.5))
        current_user.preferred_language = request.form.get('preferred_language', 'en')
        current_user.theme_mode = request.form.get('theme_mode', 'light')
        db.session.commit()
        flash(_("Preferencias actualizadas correctamente"), "success")
    except Exception as e:
        flash(_("Hubo un error al guardar tus preferencias"), "danger")

    # Redirigir de vuelta a la página anterior (donde estaba el modal)
    return redirect(request.referrer or url_for('home'))


@app.before_request
def refresh_session():
    session.modified = True  # renueva el tiempo de expiración en cada request


@app.before_request
def check_concurrent_login():
    # Evitar interferir con login/logout o recursos públicos
    if request.endpoint in ['login', 'logout', 'static']:
        return

    if current_user.is_authenticated:
        user = db.session.get(User, current_user.id)

        session_token = session.get('session_token')

        if not session_token or session_token != user.session_token:
            logout_user()
            flash(_("Tu sesión ha sido cerrada porque se inició sesión en otro dispositivo."), "warning")
            return redirect(url_for('login'))


@app.route("/login", methods=["GET", "POST"])
def login():
    if not current_user.is_authenticated and request.args.get("next"):
        flash(_("Tu sesión ha expirado por inactividad. Por favor, vuelve a iniciar sesión."), "warning")

    if request.method == "POST":
        email = request.form.get('email')
        password = request.form.get('password')
        user = User.query.filter_by(email=email).first()

        if not user or not check_password_hash(user.password, password):
            flash(_("Credenciales inválidas"), "warning")
            return redirect(url_for('login'))

        # ✅ BLOQUEA usuarios no verificados
        if not user.is_verified:
            flash(Markup(
                _("Debes confirmar tu correo antes de iniciar sesión. Revisa tu bandeja de entrada o "
                  "<a href='{url}' class='alert-link'>haz clic aquí para reenviar</a>.").format(
                    url=url_for('resend_verification', email=email)
                )
            ), "warning")
            return redirect(url_for('login'))

        # ✅ 1) Genera un token único
        new_token = secrets.token_urlsafe(32)

        # ✅ 2) Guarda en la base de datos
        user.session_token = new_token
        db.session.commit()

        # ✅ 3) Inicia sesión normal
        login_user(user)
        session.permanent = True  # timeout automático

        # ✅ 4) Guarda el token en la sesión del navegador
        session['session_token'] = new_token
        session['theme_mode'] = user.theme_mode
        session['language'] = user.preferred_language  # ✅ Seteamos el idioma en la sesión

        flash(_("Inicio de sesión exitoso"), "success")

        # ✅ Registrar en CSV
        try:
            file_exists = os.path.exists(LOG_FILE_PATH)
            with open(LOG_FILE_PATH, mode="a", newline="", encoding="utf-8") as log_file:
                writer = csv.writer(log_file)
                if not file_exists:
                    writer.writerow(["id", "username", "timestamp"])

                ny_time = datetime.now(ZoneInfo("America/New_York"))
                writer.writerow([user.id, user.username, ny_time.isoformat()])
        except Exception as e:
            print("Error al escribir en login_logs.csv:", e)

        # Verificar pago
        if not user.is_member:
            flash(_("Debes completar el pago para activar tu suscripción."), "warning")
            return redirect(url_for('stripe_payment'))

        return redirect(url_for('home'))

    return render_template('login.html')


@app.route("/reset_password", methods=["GET", "POST"])
def reset_password():
    if request.method == "POST":
        email = request.form.get("email")
        user = User.query.filter_by(email=email).first()

        if user:
            try:
                token = generate_reset_token(user.email)
                reset_url = url_for('reset_with_token', token=token, _external=True)

                # ✅ Detectar idioma actual
                lang = str(get_locale())  # 'es' o 'en'

                if lang == 'es':
                    subject = "Recuperación de contraseña - BackTestingMarket"
                    body = f"""
Hola {user.username},

Has solicitado restablecer tu contraseña.

Haz clic en el siguiente enlace para establecer una nueva contraseña:
{reset_url}

Si tú no solicitaste este cambio, puedes ignorar este correo.

Gracias,
El equipo de BackTestingMarket
"""
                else:  # fallback a inglés
                    subject = "Password Reset - BackTestingMarket"
                    body = f"""
Hi {user.username},

You requested to reset your password.

Click the link below to set a new password:
{reset_url}

If you did not request this change, you can safely ignore this message.

Thanks,
The BackTestingMarket Team
"""

                send_email_general(
                    subject=subject,
                    sender_name="BackTestingMarket",
                    sender_email="info@backtestingmarket.com",
                    receiver_email=user.email,
                    body=body
                )

            except Exception as e:
                print(f"[EMAIL ERROR] {e}")
                flash(_("Ocurrió un error al enviar el enlace de recuperación."), "danger")
                return redirect(url_for("login"))

        flash(_("Si el correo está registrado, recibirás instrucciones para restablecer tu contraseña."), "info")
        return redirect(url_for("login"))

    return render_template("reset_password.html")


@app.route("/reset_password_token/<token>", methods=["GET", "POST"])
def reset_with_token(token):
    email = verify_reset_token(token)
    if not email:
        flash(_("El enlace es inválido o ha expirado."), "danger")
        return redirect(url_for("login"))

    if request.method == "POST":
        password = request.form.get("password")
        confirm = request.form.get("confirm_password")

        if password != confirm:
            flash(_("Las contraseñas no coinciden."), "danger")
            return render_template("new_password.html", token=token)

        user = User.query.filter_by(email=email).first()
        if user:
            user.password = generate_password_hash(password)
            db.session.commit()
            flash(_("Tu contraseña ha sido restablecida con éxito."), "success")
            return redirect(url_for("login"))

    return render_template("new_password.html", token=token)


@app.route("/verify_email/<token>")
def verify_email(token):
    email = verify_verify_token(token)
    if not email:
        flash(_("El enlace de verificación es inválido o ha expirado."), "danger")
        return redirect(url_for("login"))

    user = User.query.filter_by(email=email).first()
    if not user:
        flash(_("Usuario no encontrado."), "danger")
        return redirect(url_for("login"))

    if user.is_verified:
        flash(_("Tu cuenta ya está verificada. Por favor inicia sesión."), "success")
    else:
        user.is_verified = True
        db.session.commit()
        flash(_("¡Gracias! Tu cuenta ha sido verificada."), "success")

    return redirect(url_for("login"))


@app.route("/resend_verification")
def resend_verification():
    email = request.args.get("email")
    if not email:
        flash(_("No se proporcionó correo válido."), "danger")
        return redirect(url_for("login"))

    user = User.query.filter_by(email=email).first()
    if user and not user.is_verified:
        token = generate_verify_token(user.email)
        verify_link = url_for('verify_email', token=token, _external=True)

        subject = "Confirma tu correo - BackTestingMarket"
        body = f"""
Hola {user.username},

Has solicitado reenviar el enlace para confirmar tu correo.

Haz clic aquí para verificar tu cuenta:
{verify_link}

Si tú no solicitaste este correo, ignóralo.

El equipo de BackTestingMarket
"""
        send_email_general(
            subject=subject,
            sender_name="BackTestingMarket",
            sender_email="info@backtestingmarket.com",
            receiver_email=user.email,
            body=body
        )

        flash(_("Se ha enviado un nuevo correo de verificación."), "info")
        return redirect(url_for("login"))

    flash(_("Usuario no encontrado o ya verificado."), "warning")
    return redirect(url_for("login"))


@app.route("/register", methods=["GET", "POST"])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        email = request.form.get('email')
        password = request.form.get('password')
        confirm_password = request.form.get('confirm_password')
        referrer_code = request.form.get('referrer_code')
        phone_e164 = (request.form.get('phone_e164') or '').strip() or None
        how_heard = (request.form.get('how_heard') or '').strip() or None

        # Validar reCAPTCHA
        recaptcha_response = request.form.get('g-recaptcha-response')
        secret_key = "6LfhXTUrAAAAACWDEG3jZFuMFtsG842Se5UWFcY3"
        verify_url = "https://www.google.com/recaptcha/api/siteverify"
        try:
            response = requests.post(verify_url, data={
                'secret': secret_key,
                'response': recaptcha_response
            })
            result = response.json()
        except Exception:
            flash(_("Error al verificar reCAPTCHA."), "danger")
            return render_template('register.html', username=username, email=email)

        if not result.get("success"):
            flash(_("Por favor, completa el CAPTCHA para verificar que no eres un robot."), "danger")
            return render_template('register.html', username=username, email=email)

        # Validar nombre duplicado
        if User.query.filter_by(username=username).first():
            flash(_("El nombre ya está registrado. Por favor usa uno diferente."), "warning")
            return render_template('register.html', username=username, email=email)

        # Validar email duplicado
        if User.query.filter_by(email=email).first():
            flash(_("El email ya está registrado."), "warning")
            return render_template('register.html', username=username, email=email)

        # Crear nuevo usuario con is_verified = False
        hashed_password = generate_password_hash(password)
        new_user = User(
            username=username,
            email=email,
            password=hashed_password,
            is_verified=False,
            referrer_code=referrer_code,
            phone=phone_e164,
            how_heard=how_heard,
        )

        # Generar y guardar session_token si lo usas
        new_token = secrets.token_urlsafe(32)
        new_user.session_token = new_token

        db.session.add(new_user)
        try:
            db.session.commit()
        except Exception:
            db.session.rollback()
            flash(_("Error al registrar usuario."), "danger")
            return render_template('register.html', username=username, email=email)

        # ✅ Generar token de verificación y preparar email
        token = generate_verify_token(new_user.email)
        verify_link = url_for('verify_email', token=token, _external=True)

        lang = str(get_locale())
        if lang == 'es':
            subject = "Confirma tu correo - BackTestingMarket"
            body = f"""
Hola {new_user.username},

Gracias por registrarte en BackTestingMarket.

Por favor confirma tu correo haciendo clic en este enlace:
{verify_link}

Si tú no solicitaste este registro, ignora este correo.

El equipo de BackTestingMarket
"""
        else:
            subject = "Confirm your email - BackTestingMarket"
            body = f"""
Hi {new_user.username},

Thank you for registering at BackTestingMarket.

Please confirm your email by clicking the link below:
{verify_link}

If you did not request this registration, you can safely ignore this message.

The BackTestingMarket Team
"""

        # 🔒 Enviar email con manejo de errores (no tirar 500 si el buzón no existe)
        try:
            send_email_general(
                subject=subject,
                sender_name="BackTestingMarket",
                sender_email="info@backtestingmarket.com",
                receiver_email=new_user.email,
                body=body
            )
            flash(_("Registro exitoso. Revisa tu correo para confirmar tu cuenta."), "info")
        except smtplib.SMTPRecipientsRefused as e:
            app.logger.warning("SMTPRecipientsRefused para %s: %s", new_user.email, e)
            flash(_("Tu cuenta fue creada, pero el correo de verificación fue rechazado por el proveedor."), "warning")
        except smtplib.SMTPException as e:
            app.logger.exception("Error SMTP al enviar verificación")
            flash(_("Tu cuenta fue creada, pero no pudimos enviar el correo de verificación. Inténtalo más tarde o solicita reenviar desde el login."), "warning")
        except Exception as e:
            app.logger.exception("Error inesperado al enviar verificación")
            flash(_("Tu cuenta fue creada, pero hubo un problema enviando el correo de verificación."), "warning")

        return redirect(url_for('login'))

    return render_template('register.html')


@app.route("/registerNew")
def registerNew():
    return render_template("registerNew.html")


@app.route('/logout')
def logout():
    logout_user()
    flash(_("Has cerrado sesión."), "info")
    return redirect(url_for('home'))


@app.route("/stripe_payment")
@login_required
def stripe_payment():
    return render_template("stripe_payment.html")


@app.route("/graficoIC")
def graficoIC():
    return render_template("graficoIC.html")


# ---- Funciones predictor ----
def get_available_dates(symbol):
    print(symbol)
    """Obtiene las fechas de los archivos CSV disponibles en /data."""
    files = os.listdir(PATH_UBUNTU + symbol + "/")
    dates = []
    for file in files:
        if file.startswith(f"prediction_") and file.endswith(".csv"):
            try:
                date_part = file.split("_")[-1].replace(".csv", "")
                dates.append(date_part)
            except Exception as e:
                print(f"Error procesando archivo {file}: {e}")
    return sorted(dates, reverse=True)  # Ordenar de más reciente a más antiguo


def read_csv(selected_date, symbol):
    """Lee los datos del archivo CSV según la fecha."""
    folder = symbol.replace("$", "")
    csv_file = f"{PATH_UBUNTU}/{folder}/prediction_{symbol}_{selected_date}.csv"
    try:
        df = pd.read_csv(csv_file, parse_dates=['timestamp'])
        return df.tail(390).to_dict(orient='records')
    except Exception as e:
        print(f"Error leyendo el CSV: {e}")
        return {'error': str(e)}


# ---- Rutas del predictor ----


@app.route('/get_prediction_data')
def get_prediction_data():
    date = request.args.get('date')
    risk = request.args.get('risk')
    symbol_input = request.args.get('symbol')

    # Aplicar el prefijo "$" solo a ciertos símbolos
    if symbol_input in ['SPX', 'RUT', 'XSP']:
        symbol = f"${symbol_input}"
    else:
        symbol = symbol_input

    base_path_predictions = f'/var/www/html/backtestingmarket/predictor_data/data/{symbol_input}/'
    base_path_chains = "/var/www/html/flask_project/chains/"

    pred_file = os.path.join(base_path_predictions, f'prediction_{symbol}_{date}.csv')
    chain_file = os.path.join(base_path_chains, f'optionChain_{symbol}_{date}.csv')
    print(f"[DEBUG] pred_file: {pred_file}")

    if not os.path.exists(pred_file) or not os.path.exists(chain_file):
        return jsonify({'error': 'Archivo no encontrado'}), 404

    # Cargar archivos
    df_pred = pd.read_csv(pred_file)
    df_chain = pd.read_csv(chain_file)

    # Convertir timestamp
    df_pred['timestamp'] = pd.to_datetime(df_pred['timestamp'])
    df_pred['Hora'] = df_pred['timestamp'].dt.strftime('%H:%M:%S')
    df_pred = df_pred[df_pred['timestamp'].dt.time.between(time(10, 5), time(15, 30))]
    df_pred = df_pred[df_pred['timestamp'].dt.minute % 5 == 0]

    # Parsear IDEA
    def parse_vertical(row):
        try:
            idea_col_map = {
                'conservador': 'IDEA',
                'intermedio': 'IDEA_adj5',
                'agresivo': 'IDEA_adj10',
                'ultra_agresivo': 'IDEA_adj15'
            }
            idea_col = idea_col_map.get(risk, 'IDEA')
            idea_str = row.get(idea_col, '')

            estrategia = "VERTICAL"
            simbolo_match = re.search(r'\b(SPX|RUT|QQQ|XSP|SPY)\b', idea_str)
            if not simbolo_match:
                raise ValueError(f"Símbolo no detectado en idea: {idea_str}")
            simbolo = simbolo_match.group(1)

            expiracion_raw = re.search(r'\d{2} \w{3} \d{2}', idea_str).group(0)
            expiracion = datetime.strptime(expiracion_raw, "%d %b %y").strftime("%m/%d/%Y")

            strikes = re.search(r'\d{2} \w{3} \d{2} (\d{2,5}/\d{2,5})', idea_str).group(1)
            credito = float(re.search(r'@([0-9.]+)', idea_str).group(1)) * 100

            lado = "PUT" if "PUT" in idea_str else "CALL"

            return pd.Series([estrategia, simbolo, expiracion, strikes, credito, lado])

        except Exception as e:
            print(f"[Error parse_vertical] {e}")
            return pd.Series(["", "", "", "", 0.0, ""])

    df_pred[['Estrategia', 'Simbolo', 'Expiracion', 'Strikes', 'Credito', 'Lado']] = df_pred.apply(parse_vertical, axis=1)

    # Obtener último snapshot
    df_chain['timestamp'] = pd.to_datetime(df_chain['timestamp'])
    last_snapshot = df_chain['timestamp'].max()
    df_last_chain = df_chain[df_chain['timestamp'] == last_snapshot]

    EARLY_CLOSE_DATES = [
        "2025-07-03",
        "2025-11-28",
        "2025-12-24",
    ]

    is_early_close = date in EARLY_CLOSE_DATES
    if is_early_close:
        usar_precio_final = last_snapshot.time() > time(13, 0)  # 🕐 1 PM para early close
    else:
        usar_precio_final = last_snapshot.time() > time(16, 0)  # 🕓 4 PM normal

    spx_close = df_last_chain['underlying_price'].iloc[0] if usar_precio_final else None

    # Calcular P/L con opciones (mercado abierto)

    def calc_pl_opciones(row):
        try:
            sell_strike, buy_strike = map(int, row['Strikes'].split('/'))
            lado = row['Lado'].upper()
            credito = row['Credito']

            if lado == 'PUT':
                sell_row = df_last_chain[df_last_chain['strike'] == sell_strike].iloc[0]
                buy_row = df_last_chain[df_last_chain['strike'] == buy_strike].iloc[0]
                sell_mid = (float(sell_row['bid_put']) + float(sell_row['ask_put'])) / 2
                buy_mid = (float(buy_row['bid_put']) + float(buy_row['ask_put'])) / 2
            elif lado == 'CALL':
                sell_row = df_last_chain[df_last_chain['strike'] == sell_strike].iloc[0]
                buy_row = df_last_chain[df_last_chain['strike'] == buy_strike].iloc[0]
                sell_mid = (float(sell_row['bid_call']) + float(sell_row['ask_call'])) / 2
                buy_mid = (float(buy_row['bid_call']) + float(buy_row['ask_call'])) / 2
            else:
                return 0.0

            spread_value = sell_mid - buy_mid
            pl = round(credito - (spread_value * 100), 2)

            # print(f"[P/L MidPrice] {lado} - Venta: {sell_strike} (mid: {sell_mid:.2f}), Compra: {buy_strike} (mid: {buy_mid:.2f}) → Spread: {spread_value:.2f} → P/L: {pl}")
            return pl

        except Exception as e:
            print(f"[Error MidPrice] {e}")
            return 0.0

    # Calcular P/L con precio final (mercado cerrado)
    def calc_pl_cierre(row):
        try:
            sell_strike, buy_strike = map(int, row['Strikes'].split('/'))
            lado = row['Lado'].upper()
            credito = row['Credito']
            width = abs(sell_strike - buy_strike)

            if lado == 'PUT':
                intrinsic = max(0, sell_strike - spx_close)
            elif lado == 'CALL':
                intrinsic = max(0, spx_close - sell_strike)
            else:
                return 0.0

            intrinsic = min(intrinsic, width)
            return round(credito - (intrinsic * 100), 2)
        except:
            return 0.0

    # Seleccionar método de cálculo
    if usar_precio_final:
        df_pred['P/L'] = df_pred.apply(calc_pl_cierre, axis=1)
    else:
        df_pred['P/L'] = df_pred.apply(calc_pl_opciones, axis=1)

    # Precio actual del subyacente (último snapshot)
    underlying_price = df_last_chain['underlying_price'].iloc[0]

    # Nuevas columnas: DiffPts, DiffPct, Moneyness
    diff_pts_list = []
    diff_pct_list = []
    moneyness_list = []

    for _, row in df_pred.iterrows():
        try:
            sell_strike = int(row['Strikes'].split('/')[0])  # ✅ siempre es el primer strike
            diff_pts = round(sell_strike - underlying_price, 2)
            diff_pct = round((diff_pts / underlying_price) * 100, 2)

            strikes_raw = row['Strikes'].split('/')
            strike1 = int(strikes_raw[0])
            strike2 = int(strikes_raw[1])
            strike_low, strike_high = sorted([strike1, strike2])
            lado = row['Lado'].upper()

            if lado == 'PUT':
                if underlying_price < strike_low:
                    moneyness = 'ITM'
                elif strike_low <= underlying_price < strike_high:
                    moneyness = 'ITM P'
                else:
                    moneyness = 'OTM'
            elif lado == 'CALL':
                if underlying_price > strike_high:
                    moneyness = 'ITM'
                elif strike_low < underlying_price <= strike_high:
                    moneyness = 'ITM P'
                else:
                    moneyness = 'OTM'
            else:
                moneyness = 'N/A'
        except:
            diff_pts = 0.0
            diff_pct = 0.0
            moneyness = 'N/A'

        diff_pts_list.append(diff_pts)
        diff_pct_list.append(diff_pct)
        moneyness_list.append(moneyness)

    df_pred['DiffPts'] = diff_pts_list
    df_pred['DiffPct'] = diff_pct_list
    df_pred['Moneyness'] = moneyness_list
    df_pred['Expiracion'] = datetime.strptime(date, "%Y-%m-%d").strftime("%m/%d/%Y")

    df_pred['precio_actual'] = df_pred['precio_actual']
    df_pred['tendencia_30min'] = df_pred['tendencia_30min']
    df_pred['tendencia_corta'] = df_pred['tendencia_corta']
    df_pred['score_30min'] = df_pred['score_30min']
    df_pred['strikes_OI'] = df_pred['strikes_OI']
    df_pred['strikes_GEX'] = df_pred['strikes_GEX']
    df_pred['strikes_Volumen'] = df_pred['strikes_Volumen']
    df_pred['total_pos'] = df_pred['total_pos']
    df_pred['total_neg'] = df_pred['total_neg']

    # Armar DataFrame final con todas las columnas necesarias
    df_final = df_pred[['Hora', 'Estrategia', 'Simbolo', 'Expiracion', 'Lado', 'Strikes', 'Credito',
                        'DiffPts', 'DiffPct', 'Moneyness', 'P/L',
                        'precio_actual', 'tendencia_30min', 'tendencia_corta', 'score_30min',
                        'strikes_OI', 'strikes_GEX', 'strikes_Volumen',
                        'total_pos', 'total_neg']]
    return jsonify({
        'spx_final': round(spx_close if usar_precio_final else df_last_chain['underlying_price'].iloc[0], 2),
        'data': df_final.to_dict(orient='records')
    })



@app.route('/get_prediction_data_ic')
def get_prediction_data_ic():

    date = request.args.get('date')
    risk = request.args.get('risk')
    symbol_input = request.args.get('symbol')

    # Aplicar el prefijo "$" solo a ciertos símbolos
    if symbol_input in ['SPX', 'RUT', 'XSP']:
        symbol = f"${symbol_input}"
    else:
        symbol = symbol_input

    # Definir rutas de archivos
    base_path_predictions = f'/var/www/html/backtestingmarket/predictor_data/data/{symbol_input}/'
    base_path_chains = "/var/www/html/flask_project/chains/"

    pred_file = os.path.join(base_path_predictions, f'prediction_{symbol}_{date}.csv')
    chain_file = os.path.join(base_path_chains, f'optionChain_{symbol}_{date}.csv')
    print(f"[DEBUG] pred_file: {pred_file}")

    if not os.path.exists(pred_file) or not os.path.exists(chain_file):
        return jsonify({'error': 'Archivo no encontrado'}), 404

    # Cargar archivos
    df_pred = pd.read_csv(pred_file)
    df_chain = pd.read_csv(chain_file)

    # Convertir timestamps
    df_pred['timestamp'] = pd.to_datetime(df_pred['timestamp'])
    df_pred['Hora'] = df_pred['timestamp'].dt.strftime('%H:%M:%S')
    df_pred = df_pred[df_pred['timestamp'].dt.time.between(time(10, 5), time(15, 30))]
    df_pred = df_pred[df_pred['timestamp'].dt.minute % 5 == 0]

    df_chain['timestamp'] = pd.to_datetime(df_chain['timestamp'])
    last_snapshot = df_chain['timestamp'].max()
    df_last_chain = df_chain[df_chain['timestamp'] == last_snapshot]

    usar_precio_final = last_snapshot.time() > time(16, 0)
    spx_close = df_last_chain['underlying_price'].iloc[0] if usar_precio_final else None

   # Parsear IDEA_IC
    def parse_ic(row):
        try:
            idea_col_map = {
                'conservador': 'IDEA_IC',
                'intermedio': 'IDEA_IC_adj5',
                'agresivo': 'IDEA_IC_adj10',
                'ultra_agresivo': 'IDEA_IC_adj15'
            }
            idea_col = idea_col_map.get(risk, 'IDEA_IC')
            idea_str = row.get(idea_col, '')

            estrategia = "IRON CONDOR"

            simbolo_match = re.search(r'\b(SPX|RUT|QQQ|XSP|SPY)\b', idea_str)
            if not simbolo_match:
                raise ValueError(f"Símbolo no detectado en idea: {idea_str}")
            simbolo = simbolo_match.group(1)

            expiracion_raw = re.search(r'\d{2} \w{3} \d{2}', idea_str).group(0)
            expiracion = datetime.strptime(expiracion_raw, "%d %b %y").strftime("%m/%d/%Y")

            calls_strikes = re.search(r'\[CALLS (\d{2,5}/\d{2,5})\]', idea_str).group(1)
            puts_strikes = re.search(r'\[PUTS (\d{2,5}/\d{2,5})\]', idea_str).group(1)
            strikes_ic = f"{puts_strikes}-{calls_strikes}"

            credito = float(re.search(r'@([0-9.]+)', idea_str).group(1)) * 100

            return pd.Series([estrategia, simbolo, expiracion, strikes_ic, credito])
        except Exception as e:
            print(f"[Error parse_ic] {e}")
            return pd.Series(["", "", "", "", 0.0])

    df_pred[['Estrategia', 'Simbolo', 'Expiracion', 'Strikes_IC', 'Credito']] = df_pred.apply(parse_ic, axis=1)
    df_pred = df_pred[df_pred['Estrategia'] == "IRON CONDOR"]

    # Función P/L con precios de opciones (mercado abierto)
    def calc_pl_opciones_ic(row):
        try:
            puts_part, calls_part = row['Strikes_IC'].split('-')
            put_sell, put_buy = map(int, puts_part.split('/'))
            call_sell, call_buy = map(int, calls_part.split('/'))

            put_sell_row = df_last_chain[df_last_chain['strike'] == put_sell].iloc[0]
            put_buy_row = df_last_chain[df_last_chain['strike'] == put_buy].iloc[0]
            call_sell_row = df_last_chain[df_last_chain['strike'] == call_sell].iloc[0]
            call_buy_row = df_last_chain[df_last_chain['strike'] == call_buy].iloc[0]

            mid_put_sell = (put_sell_row['bid_put'] + put_sell_row['ask_put']) / 2
            mid_put_buy = (put_buy_row['bid_put'] + put_buy_row['ask_put']) / 2
            mid_call_sell = (call_sell_row['bid_call'] + call_sell_row['ask_call']) / 2
            mid_call_buy = (call_buy_row['bid_call'] + call_buy_row['ask_call']) / 2

            put_spread_value = abs(mid_put_buy - mid_put_sell)
            call_spread_value = abs(mid_call_buy - mid_call_sell)

            spread_width = abs(put_sell - put_buy)

            total_spread_value = put_spread_value + call_spread_value
            total_spread_value = min(total_spread_value, spread_width)

            pl = round(row['Credito'] - total_spread_value * 100, 2)
            return pl
        except Exception as e:
            print(f"[Error calc_pl_opciones_ic] {e}")
            return 0.0

    # Función P/L con precio de cierre (mercado cerrado)
    def calc_pl_cierre_ic(row):
        try:
            puts_part, calls_part = row['Strikes_IC'].split('-')
            put_sell, put_buy = map(int, puts_part.split('/'))
            call_sell, call_buy = map(int, calls_part.split('/'))

            spread_width = abs(put_sell - put_buy)

            intrinsic_put = max(0, put_sell - spx_close)
            intrinsic_call = max(0, spx_close - call_sell)

            intrinsic_total = intrinsic_put + intrinsic_call
            intrinsic_total = min(intrinsic_total, spread_width)

            pl = round(row['Credito'] - intrinsic_total * 100, 2)
            return pl
        except Exception as e:
            print(f"[Error calc_pl_cierre_ic] {e}")
            return 0.0

    # Asignar P/L
    if usar_precio_final:
        df_pred['P/L'] = df_pred.apply(calc_pl_cierre_ic, axis=1)
    else:
        df_pred['P/L'] = df_pred.apply(calc_pl_opciones_ic, axis=1)

    # Precio subyacente actual
    underlying_price = spx_close if usar_precio_final else df_last_chain['underlying_price'].iloc[0]

    # Distancias, Porcentajes y Moneyness
    def calc_distancias(row):
        try:
            puts_part, calls_part = row['Strikes_IC'].split('-')
            put_sell, put_buy = map(int, puts_part.split('/'))
            call_sell, call_buy = map(int, calls_part.split('/'))

            diff_pts_put = round(underlying_price - put_sell, 2)
            diff_pts_call = round(underlying_price - call_sell, 2)

            diff_pct_put = round((diff_pts_put / put_sell) * 100, 2)
            diff_pct_call = round((diff_pts_call / call_sell) * 100, 2)

            moneyness_put = (
                "OTM" if underlying_price > put_sell else
                "ITM P" if put_buy < underlying_price <= put_sell else
                "ITM"
            )

            moneyness_call = (
                "OTM" if underlying_price < call_sell else
                "ITM P" if call_sell <= underlying_price < call_buy else
                "ITM"
            )

            return pd.Series([diff_pts_put, diff_pts_call, diff_pct_put, diff_pct_call, moneyness_put, moneyness_call])
        except Exception as e:
            print(f"[Error calc_distancias] {e}")
            return pd.Series([0.0, 0.0, 0.0, 0.0, "N/A", "N/A"])

    df_pred[['DiffPts_PUT', 'DiffPts_CALL', 'DiffPct_PUT', 'DiffPct_CALL', 'Moneyness_PUT', 'Moneyness_CALL']] = df_pred.apply(calc_distancias, axis=1)
    df_pred['Expiracion'] = datetime.strptime(date, "%Y-%m-%d").strftime("%m/%d/%Y")

    df_pred['precio_actual'] = df_pred['precio_actual']
    df_pred['tendencia_30min'] = df_pred['tendencia_30min']
    df_pred['tendencia_corta'] = df_pred['tendencia_corta']
    df_pred['score_30min'] = df_pred['score_30min']
    df_pred['strikes_OI'] = df_pred['strikes_OI']
    df_pred['strikes_GEX'] = df_pred['strikes_GEX']
    df_pred['strikes_Volumen'] = df_pred['strikes_Volumen']
    df_pred['total_pos'] = df_pred['total_pos']
    df_pred['total_neg'] = df_pred['total_neg']

    result = df_pred[['Hora', 'Estrategia', 'Simbolo', 'Expiracion', 'Strikes_IC', 'Credito',
                      'P/L', 'DiffPts_PUT', 'DiffPts_CALL', 'DiffPct_PUT', 'DiffPct_CALL',
                      'Moneyness_PUT', 'Moneyness_CALL',
                      'precio_actual', 'tendencia_30min', 'tendencia_corta', 'score_30min',
                      'strikes_OI', 'strikes_GEX', 'strikes_Volumen',
                      'total_pos', 'total_neg']]

    return jsonify({
        'spx_final': round(underlying_price, 2),
        'data': result.to_dict(orient='records')
    })


@app.route('/data_gex')
def get_data_gex():
    selected_date = request.args.get('date')
    horario = request.args.get('horario', '11:15')
    symbol = request.args.get('symbol', 'SPX')

    if not selected_date:
        selected_date = datetime.today().strftime('%Y-%m-%d')
    return jsonify(read_csv_gex(selected_date, horario, symbol))


def read_csv_gex(selected_date, horario, symbol):
    # Agregar $ si es un índice
    indices = {"SPX", "RUT", "XSP"}
    if symbol in indices:
        symbol_filename = f"${symbol}"
    else:
        symbol_filename = symbol

    csv_file = f"/var/www/html/flask_project/chains/optionChain_{symbol_filename}_{selected_date}.csv"

    try:
        # Leer todo el CSV y convertir 'timestamp' a datetime
        df = pd.read_csv(csv_file, parse_dates=['timestamp'])

        # Construir el timestamp objetivo a partir de la fecha y el horario definidos
        target_time = pd.to_datetime(f"{selected_date} {horario}")

        # Encontrar el timestamp más cercano
        idx = (df['timestamp'] - target_time).abs().idxmin()
        target_snapshot_time = df.loc[idx, 'timestamp']

        # Filtrar el snapshot (todas las filas con ese timestamp)
        snapshot_df = df[df['timestamp'] == target_snapshot_time].copy()

        # Multiplicador (por contrato, típicamente 100)
        multiplicador = 100

        # Flag para cambiar método de cálculo
        usar_volumen_intradia = True  # Cambiar a False para usar solo OI

        if usar_volumen_intradia:
            # Método híbrido con alpha y volumen para aproximar OI en 0DTE
            alpha = 25000

            snapshot_df['estimated_OI_call'] = snapshot_df['open_interest_call'] + alpha * snapshot_df['volume_call']
            snapshot_df['estimated_OI_put'] = snapshot_df['open_interest_put'] + alpha * snapshot_df['volume_put']

            snapshot_df['GEX_call'] = snapshot_df['gamma_call'] * snapshot_df['estimated_OI_call'] * multiplicador
            snapshot_df['GEX_put'] = snapshot_df['gamma_put'] * snapshot_df['estimated_OI_put'] * multiplicador

        else:
            # Método clásico con OI
            snapshot_df['GEX_call'] = snapshot_df['gamma_call'] * snapshot_df['open_interest_call'] * multiplicador
            snapshot_df['GEX_put'] = snapshot_df['gamma_put'] * snapshot_df['open_interest_put'] * multiplicador

        # Para graficar puts como valores negativos
        snapshot_df.loc[:, 'GEX_put_neg'] = -snapshot_df['GEX_put']

        # Agrupar por strike (se asume que cada snapshot contiene 80 strikes)
        grouped = snapshot_df.groupby('strike').agg({
            'underlying_price': 'first',
            'GEX_call': 'sum',
            'GEX_put_neg': 'sum'
        }).reset_index()

        # Calcular GEX neto
        grouped.loc[:, 'GEX_net'] = grouped['GEX_call'] + grouped['GEX_put_neg']

        # Filtrar solo strikes con GEX_net distinto a 0
        grouped = grouped[grouped['GEX_net'] != 0]

        # Escalar los valores a millones (divide por 1e6)
        grouped.loc[:, 'GEX_call'] /= 1e6
        grouped.loc[:, 'GEX_put_neg'] /= 1e6
        grouped.loc[:, 'GEX_net'] /= 1e6

        # Retornar un diccionario que incluya el timestamp del snapshot y los datos agrupados
        result = {
            "snapshot_time": target_snapshot_time.strftime("%Y-%m-%d %H:%M:%S"),
            "data": grouped.to_dict(orient='records')
        }
        return result
    except Exception as e:
        print(f"Error leyendo el CSV: {e}")
        return {'error': str(e)}




#***********************************************************
# Caché en memoria por símbolo
DATES_CACHE = {}
CACHE_TTL = 60  # segundos, puedes subirlo si quieres

def _read_available_dates(symbol: str):
    """
    Lee realmente el disco y devuelve la lista de fechas disponibles
    para un símbolo.
    """
    path = os.path.join(PATH_UBUNTU, symbol)
    try:
        files = os.listdir(path)
    except FileNotFoundError:
        return []

    dates = []
    for file in files:
        if file.startswith('prediction_') and file.endswith('.csv'):
            try:
                date_part = file.split('_')[-1].replace('.csv', '')
                dates.append(date_part)
            except Exception as e:
                print(f"[Error procesando archivo {file}: {e}]")

    return sorted(dates)


def get_available_dates_cached(symbol: str):
    """
    Versión cacheada con TTL.
    Varios usuarios pueden leer desde RAM sin tocar disco.
    """
    # Usamos datetime en vez de time.time()
    now = datetime.now().timestamp()
    entry = DATES_CACHE.get(symbol)

    # Cache válido → devolvemos directamente
    if entry and (now - entry['ts'] < CACHE_TTL):
        return entry['dates']

    # Cache vencido o inexistente → leer del disco
    dates = _read_available_dates(symbol)
    DATES_CACHE[symbol] = {
        'ts': now,
        'dates': dates,
    }
    return dates


@app.route('/get_dates')
def get_dates():
    selected_symbol = request.args.get('symbol')
    if not selected_symbol:
        return jsonify([])

    fechas = get_available_dates_cached(selected_symbol)
    return jsonify(fechas)



# #***********************************************************

# from functools import lru_cache

# @lru_cache(maxsize=16)
# def get_available_dates_cached(symbol):
#     """
#     Versión cacheada en memoria de las fechas disponibles para un símbolo.
#     Se ejecuta una sola vez por valor distinto de `symbol`
#     dentro de cada proceso de WSGI.
#     """
#     path = os.path.join(PATH_UBUNTU, symbol)
#     try:
#         files = os.listdir(path)
#     except FileNotFoundError:
#         return []

#     dates = []
#     for file in files:
#         if file.startswith("prediction_") and file.endswith(".csv"):
#             try:
#                 date_part = file.split("_")[-1].replace(".csv", "")
#                 dates.append(date_part)
#             except Exception as e:
#                 print(f"[Error procesando archivo {file}: {e}")
#     # si quieres muchas fechas, déjalas todas; si no, puedes limitar
#     # dates = sorted(dates, reverse=True)[:120]  # por ejemplo solo últimos 120 días
#     return sorted(dates)  # puedes decidir orden asc/desc y luego manejarlo en el front



# @app.route('/get_dates')
# def get_dates():
#     selected_symbol = request.args.get('symbol')
#     if not selected_symbol:
#         return jsonify([])

#     fechas = get_available_dates_cached(selected_symbol)
#     return jsonify(fechas)


# @app.route('/get_dates')
# def get_dates():
#     selected_symbol = request.args.get('symbol')
#     print("[DEBUG] selected_symbol:", selected_symbol)
#     return jsonify(get_available_dates(selected_symbol))



#***********************************************************

@app.route('/data')
def get_data():
    selected_date = request.args.get('date')
    selected_symbol = request.args.get('symbol')

    # Agregar "$" solo si corresponde
    if selected_symbol in ['SPX', 'RUT', 'XSP']:
        normalized_symbol = f"${selected_symbol}"
    else:
        normalized_symbol = selected_symbol

    if not selected_date:
        selected_date = datetime.today().strftime('%Y-%m-%d')

    return jsonify(read_csv(selected_date, normalized_symbol))


@app.route('/predictor')
@login_required
def predictor_page():
    return render_template('predictor.html', herramienta_actual=_("Predictor"))

@app.route('/predictor2')
@login_required
def predictor2_page():
    return render_template('predictor2.html', herramienta_actual=_("Predictor"))


@app.route('/trendIdea')
@login_required
def trendIdea():
    return render_template('trendIdea.html', herramienta_actual=_("Trend Idea"))


@app.route('/ccitm')
@login_required
def ccitm_page():
    # Lee el CSV (ajusta la ruta según corresponda)
    df = pd.read_csv('/var/www/html/flask_project/IVRank.csv')
    headers = df.columns.tolist()
    data = df.to_dict(orient='records')
    return render_template('ccitm.html', headers=headers, data=data, herramienta_actual=_("ITMCC"))


@app.route('/neutralEdge')
@login_required
def neutralEdge_page():
    return render_template('neutralEdge.html', herramienta_actual=_("Neutral Edge"))


@app.route('/backtestingIdea')
@login_required
def backtestingIdea_page():
    return render_template(
        'backtestingIdea.html',
        herramienta_actual=_("Backtesting Idea"),
        user_contracts=current_user.contracts_per_trade,
        user_commission=current_user.commission_per_leg
    )


@app.route('/backtestingIdea1')
@login_required
def backtestingIdea1_page():
    return render_template(
        'backtestingIdea1.html',
        herramienta_actual=_("Backtesting Idea"),
        user_contracts=current_user.contracts_per_trade,
        user_commission=current_user.commission_per_leg
    )


@app.route('/backtestingIdeaDashboard')
@login_required
def backtestingIdeaDashboard_page():
    return render_template(
        'backtestingIdeaDashboard.html',
        herramienta_actual=_("Backtesting Idea"),
        user_contracts=current_user.contracts_per_trade,
        user_commission=current_user.commission_per_leg
    )


@app.route('/compareIdea')
@login_required
def compareIdea_page():
    return render_template('compareIdea.html', herramienta_actual=_("Comparar Estrategias"))


@app.route('/trial_success')
def trial_success():
    return render_template('trial_success.html')


@app.route("/generar_informe_ai")
def generar_informe_ai():
    try:
        symbol = request.args.get("symbol", "SPX").upper()
        today = date.today().strftime("%Y-%m-%d")
        prefix = "$" if symbol in ["SPX", "RUT", "XSP"] else ""
        csv_path = f"/var/www/html/backtestingmarket/predictor_data/data/{symbol}/prediction_{prefix}{symbol}_{today}.csv"

        df = pd.read_csv(csv_path)
        last_row = df.iloc[-1]

        # Extraer campos clave
        timestamp = last_row['timestamp']
        precio = last_row['precio_actual']
        tendencia = last_row['tendencia_30min']
        corta = last_row['tendencia_corta']
        score = round(last_row['score_30min'], 4)

        idioma = str(flask_babel_get_locale())

        if idioma == "es":
            prompt = f"""
Eres un analista financiero profesional. Con base en los siguientes datos del índice {symbol}, genera un análisis claro, profesional y técnico en español:

- Última lectura: {timestamp}
- Precio actual: {precio}
- Tendencia intradía: {tendencia}
- Tendencia corta: {corta}
- Score de momentum: {score}

🛑 No muestres, evalúes ni describas ninguna estrategia de opciones como Iron Condor, Vertical, Spread, Calls o Puts.

Limítate a analizar: la fuerza de tendencia, el momentum, y cualquier señal de dirección probable del mercado.
"""
        else:
            prompt = f"""
You are a professional financial analyst. Based on the following data for the {symbol} index, generate a clear, professional and technical analysis in English:

- Last reading: {timestamp}
- Current price: {precio}
- Intraday trend: {tendencia}
- Short-term trend: {corta}
- Momentum score: {score}

🛑 Do not mention, evaluate or describe any options strategies like Iron Condor, Vertical, Spread, Calls or Puts.

Focus only on analyzing trend strength, momentum, and any potential directional market signal.
"""

        # Llamada a OpenAI con contexto adicional para interpretar el score
        client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=[
                {
                    "role": "system",
                    "content": (
                        "You are an expert in index and options trading.\n"
                        "- Treat 'Intraday trend' as the expected direction until end of day (0DTE), not just past 30 minutes.\n"
                        "- The 'score' ranges from -0.5 to 0.5 and indicates momentum strength and direction:\n"
                        "  • From -0.05 to 0.05 = weak trend (easily changeable).\n"
                        "  • From ±0.05 to ±0.15 = medium strength.\n"
                        "  • Greater than ±0.15 = strong trend.\n"
                        "- Positive score = bullish tendency, negative = bearish.\n"
                        "Use this to enhance your interpretation."
                    )
                },
                {"role": "user", "content": prompt}
            ]
        )

        informe_ai = response.choices[0].message.content
        informe_gex = analizar_strikes_dominantes(df)

        return Markup(f"{informe_ai}<hr>{informe_gex}")

    except Exception:
        if str(flask_babel_get_locale()) == "es":
            return "❌ No fue posible generar el informe. Intente nuevamente en unos mintuos..."
        else:
            return "❌ Unable to generate the report. Please try again in a few minutes..."


def analizar_strikes_dominantes(df):
    idioma = str(flask_babel_get_locale())

    counter = Counter()

    for val in df["strikes_GEX"]:
        try:
            parsed = json.loads(val)
            if isinstance(parsed, list) and len(parsed) > 0:
                strike = round(float(parsed[0]))  # usar solo el primario
                counter[strike] += 1
        except Exception:
            continue

    strikes_ordenados = counter.most_common(5)
    if not strikes_ordenados:
        return "No se detectaron strikes dominantes en el día." if idioma == "es" else "No dominant strikes detected today."

    top_strikes = [str(s[0]) for s in strikes_ordenados]
    mas_dominante = strikes_ordenados[0][0]

    if idioma == "es":
        return f"""📌 **Strikes Dominantes del Día:**
Los strikes más frecuentemente dominantes han sido: {', '.join(top_strikes)}.
El strike más persistente es el **{mas_dominante}**, lo cual sugiere una zona clave de control gamma o riesgo.

Este análisis puede utilizarse para identificar niveles potenciales de soporte/resistencia intradía.
"""
    else:
        return f"""📌 **Dominant Strikes of the Day:**
The most frequently dominant strikes were: {', '.join(top_strikes)}.
The most persistent strike was **{mas_dominante}**, suggesting a key gamma or dealer risk control zone.

This analysis can help identify potential intraday support/resistance levels.
"""


@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        name = request.form['name']
        email = request.form['email']
        message = request.form['message']

        cuerpo = f"""Nuevo mensaje de contacto:

Nombre: {name}
Email: {email}

Mensaje:
{message}
"""

        try:
            send_email_general(
                subject="Mensaje desde el sitio web",
                sender_name=name,
                sender_email=email,
                receiver_email="info@backtestingmarket.com",
                body=cuerpo
            )
            flash(_("Tu mensaje ha sido enviado correctamente."), "success")
        except Exception as e:
            flash(_("Ocurrió un error al enviar el mensaje. Inténtalo más tarde."), "danger")
            print(f"[EMAIL ERROR] {e}")

        return redirect(url_for('contact'))

    return render_template('contact.html')


def send_email_general(subject, sender_name, sender_email, receiver_email, body):
    email_server = 'smtp.titan.email'
    email_port = 587
    email_username = 'info@backtestingmarket.com'
    email_password = os.environ.get("EMAIL_PASSWORD")

    msg = MIMEMultipart()
    msg['From'] = formataddr((str(Header(sender_name, 'utf-8')), email_username))
    msg['To'] = receiver_email
    msg['Subject'] = Header(subject, 'utf-8')
    msg.attach(MIMEText(body, 'plain', 'utf-8'))

    with smtplib.SMTP(email_server, email_port) as server:
        server.starttls()
        server.login(email_username, email_password)
        server.sendmail(email_username, receiver_email, msg.as_string().encode('utf-8'))


def generate_reset_token(user_email):
    return serializer.dumps(user_email, salt="reset-password-salt")


def verify_reset_token(token, expiration=3600):
    try:
        email = serializer.loads(token, salt="reset-password-salt", max_age=expiration)
    except Exception:
        return None
    return email


def generate_verify_token(user_email):
    return serializer.dumps(user_email, salt="email-verify-salt")


def verify_verify_token(token, expiration=3600):
    try:
        email = serializer.loads(token, salt="email-verify-salt", max_age=expiration)
    except Exception:
        return None
    return email


schwab_client = get_schwab_client(verbose=True)


def get_magnificent_data():
    client = get_schwab_client(verbose=True)
    if not client:
        return []

    tickers = {
        "AAPL": "Apple",
        "MSFT": "Microsoft",
        "AMZN": "Amazon",
        "GOOGL": "Alphabet (Google)",
        "META": "Meta Platforms",
        "TSLA": "Tesla",
        "NVDA": "Nvidia"
    }

    def safe_call(func, *args, **kwargs):
        try:
            return func(*args, **kwargs).json()
        except Exception as e:
            print(f"⚠ Error en llamada Schwab: {e}. Reintentando...")
            new_client = get_schwab_client(verbose=False)
            try:
                return getattr(new_client, func.__name__)(*args, **kwargs).json()
            except Exception as e2:
                print(f"❌ Falla definitiva en Schwab: {e2}")
                return {}

    quotes = safe_call(client.quotes, list(tickers.keys()))

    fundamentals = {}
    for symbol in tickers:
        data = safe_call(client.instruments, symbol, "fundamental")
        fundamentals[symbol] = data

    companies = []

    for symbol, name in tickers.items():
        q = quotes.get(symbol, {})
        raw_fundamental = fundamentals.get(symbol, {})
        instruments = raw_fundamental.get("instruments", [])

        if isinstance(instruments, list) and instruments:
            f = instruments[0].get("fundamental", {})
        else:
            f = {}

        print(f"🔍 FUNDAMENTAL de {symbol}:\n{json.dumps(f, indent=2)}")

        price = (
            q.get("quote", {}).get("lastPrice")
            or q.get("regular", {}).get("regularMarketLastPrice")
            or q.get("extended", {}).get("lastPrice")
        )

        net_change = (
            q.get("quote", {}).get("netChange")
            or q.get("regular", {}).get("regularMarketNetChange")
            or q.get("extended", {}).get("markChange")
        )

        previous_close = q.get("quote", {}).get("closePrice") or price or 1
        change_pct = round((net_change / previous_close) * 100, 2) if net_change else 0.0

        pe_ratio = f.get("peRatio") or f.get("pe_ratio")
        eps = f.get("epsTTM") or f.get("eps") or f.get("eps_ttm")
        market_cap = int(f.get("marketCap") or f.get("market_cap") or 0)
        sector = f.get("sector", "Tecnología")

        # Calcular RSI y su interpretación
        rsi = calcular_rsi(symbol, client)
        trend_label = interpretar_rsi(rsi)

        company_data = {
            "name": name,
            "ticker": symbol,
            "logo": f"/static/images/logos/{symbol.lower()}.png",
            "sector": sector,
            "price": round(price, 2) if price else None,
            "change_pct": change_pct,
            "pe_ratio": round(pe_ratio, 2) if pe_ratio else None,
            "eps": round(eps, 2) if eps else None,
            "rsi": rsi if rsi is not None else "None",
            "trend_label": trend_label,
            "market_cap": format_market_cap(market_cap),
            "market_cap_raw": market_cap,
            "atr": calcular_atr(symbol, client),
            "volume_rel": calcular_volumen_relativo(symbol, client),
        }

        print(f"[DEBUG] company_data = {company_data} ✅")

        companies.append(company_data)  # ✅ Siempre es dict

    print(f"=== DEBUG FINAL: companies ===")
    print(f"=== DEBUG FINAL: companies ===")
    for idx, c in enumerate(companies):
        print(f"[{idx}] {type(c)} -> {c}")

    # ✅ Agrega los scores normalizados para Plotly
    for c in companies:
        c['pct_score'] = round(max(-1, min(1, c['change_pct'] / 5)), 4)
        c['rsi_score'] = round((c['rsi'] - 50) / 50, 4) if isinstance(c['rsi'], (float, int)) else 0
        c['atr_score'] = round(min(1, c['atr'] / 10), 4) if c['atr'] else 0
        c['vol_score'] = round(max(-1, min(1, c['volume_rel'] - 1)), 4) if c['volume_rel'] else 0

    print("=== DEBUG FINAL CON SCORES ===")
    for idx, c in enumerate(companies):
        print(
            f"[{idx}] {c['ticker']} -> pct_score: {c['pct_score']}, "
            f"rsi_score: {c['rsi_score']}, atr_score: {c['atr_score']}, vol_score: {c['vol_score']}"
        )

    return companies


def format_market_cap(value):
    if value >= 1_000_000_000_000:
        return f"{value / 1_000_000_000_000:.1f}T"
    elif value >= 1_000_000_000:
        return f"{value / 1_000_000_000:.1f}B"
    elif value >= 1_000_000:
        return f"{value / 1_000_000:.1f}M"
    return str(value)


def calcular_rsi(symbol, client, period=14):

    try:
        candles = client.price_history(symbol).json()
        closes = [c["close"] for c in candles.get("candles", [])][-period*2:]

        if len(closes) < period + 1:
            return None

        diffs = np.diff(closes)
        gains = np.where(diffs > 0, diffs, 0)
        losses = np.where(diffs < 0, -diffs, 0)

        avg_gain = np.mean(gains[:period])
        avg_loss = np.mean(losses[:period])

        for i in range(period, len(gains)):
            avg_gain = (avg_gain * (period - 1) + gains[i]) / period
            avg_loss = (avg_loss * (period - 1) + losses[i]) / period

        rs = avg_gain / avg_loss if avg_loss != 0 else 0
        rsi = 100 - (100 / (1 + rs))
        return round(rsi, 2)

    except Exception as e:
        print(f"Error calculando RSI para {symbol}: {e}")
        return None


def interpretar_rsi(rsi):
    if rsi is None:
        return "N/D"
    if rsi < 30:
        return "Sobrevendido"
    elif rsi > 70:
        return "Sobrecomprado"
    elif 45 < rsi < 55:
        return "Neutro"
    elif rsi >= 55:
        return "Alcista"
    elif rsi <= 45:
        return "Bajista"
    return "N/D"


def calculate_market_score(companies):
    """
    Combina: % cambio, RSI, ATR, Vol Rel, ponderado por Market Cap.
    También guarda cada sub-score por símbolo para graficar.
    """
    total = 0
    weight_sum = 0

    for c in companies:
        pct = c.get("change_pct", 0) or 0
        rsi = c.get("rsi", 50) or 50
        atr = c.get("atr", 0) or 0
        vol_rel = c.get("volume_rel", 1) or 1
        mcap = c.get("market_cap_raw", 1) or 1

        # Sub-scores normalizados
        pct_score = max(-1, min(1, pct / 5))
        rsi_score = (rsi - 50) / 50
        atr_score = min(1, atr / 10)
        vol_score = max(-1, min(1, vol_rel - 1))

        # Guarda cada sub-score para graficar después
        c["pct_score"] = round(pct_score, 3)
        c["rsi_score"] = round(rsi_score, 3)
        c["atr_score"] = round(atr_score, 3)
        c["vol_score"] = round(vol_score, 3)

        # Score final ponderado por market cap
        final = (
            pct_score * 0.3 +
            rsi_score * 0.3 +
            atr_score * 0.2 +
            vol_score * 0.2
        )

        total += final * mcap
        weight_sum += mcap

    score = (total / weight_sum) * 100 if weight_sum else 0
    return round(score, 2)


def calcular_atr(symbol, client, period=14):
    """
    Calcula ATR (Average True Range) básico de velas históricas.
    """
    candles = client.price_history(symbol).json().get("candles", [])
    if len(candles) < period + 1:
        return None

    tr_values = []
    for i in range(1, len(candles)):
        high = candles[i]["high"]
        low = candles[i]["low"]
        prev_close = candles[i-1]["close"]
        tr = max(high - low, abs(high - prev_close), abs(low - prev_close))
        tr_values.append(tr)

    atr = sum(tr_values[-period:]) / period
    return round(atr, 2)


def calcular_volumen_relativo(symbol, client, days=20):
    """
    Volumen actual dividido promedio últimos 20 días.
    """
    candles = client.price_history(symbol).json().get("candles", [])
    if len(candles) < days + 1:
        return 1

    today_volume = candles[-1]["volume"]
    avg_volume = sum(c["volume"] for c in candles[-days-1:-1]) / days
    if avg_volume == 0:
        return 1

    return round(today_volume / avg_volume, 2)


@app.route("/magnificent")
@login_required
def magnificent_page():
    companies = get_magnificent_data()  # Llama Schwab + RSI + ATR + VolRel

    # Agrega ATR y Volumen Rel para cada compañía
    for c in companies:
        c["atr"] = calcular_atr(c["ticker"], schwab_client)
        c["volume_rel"] = calcular_volumen_relativo(c["ticker"], schwab_client)
        c["market_cap_raw"] = int(c.get("market_cap_raw", 1))  # Asegura ponderación

    # ✅ Calcula el score REAL
    market_score = calculate_market_score(companies)

    return render_template(
        "magnificent.html",
        companies=companies,
        market_score=market_score,
        dark_mode=session.get('themeMode') == 'dark'
    )


stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
endpoint_secret = os.getenv("STRIPE_WEBHOOK_SECRET")


@csrf.exempt
@app.route("/create-checkout-session", methods=["POST"])
@login_required
def create_checkout_session():
    data = request.get_json(silent=True) or {}
    plan_type = data.get("plan_type")

    # Defaults (Stripe price_ids sin descuento)
    NORMAL_MONTHLY = "price_1RjuuYEiGxFFi4pPy361fuee"
    NORMAL_ANNUAL  = "price_1RjuuYEiGxFFi4pPXIh1pcNx"

    # Resolver Youtuber por referrer_code (si aplica)
    referrer_code = (current_user.referrer_code or "").strip()
    y = Youtuber.query.filter_by(referrer_code=referrer_code).first() if referrer_code else None

    # Determinar price_id y días de prueba (base)
    if plan_type == "mensual":
        price_id = NORMAL_MONTHLY
        trial_days = 7
    elif plan_type == "anual":
        price_id = NORMAL_ANNUAL
        trial_days = 15
    else:
        return jsonify(error="plan_type inválido"), 400

    # ------ ELEGIBILIDAD DE TRIAL ------
    eligible_for_trial = True
    if getattr(current_user, "subscription_id", None):
        eligible_for_trial = False
    # (Opcional) doble-check en Stripe por si la DB no lo tiene aún
    # try:
    #     if current_user.stripe_customer_id:
    #         subs = stripe.Subscription.list(customer=current_user.stripe_customer_id, status="all", limit=1)
    #         if subs.data:
    #             eligible_for_trial = False
    # except Exception:
    #     pass
    # -----------------------------------

    # Armar datos de la suscripción
    subscription_data = {}
    if trial_days > 0 and eligible_for_trial:
        subscription_data["trial_period_days"] = trial_days

    # Metadata adicional útil
    subscription_data["metadata"] = {
        "plan_type": plan_type,
        "user_id": str(current_user.id),
        "referrer_code": referrer_code,
        "youtuber_id": (str(y.id) if y else "")
    }

    # ----------------- CUSTOMER STRATEGY (no pre-crear) -----------------
    # 1) Si ya tenemos customer guardado -> úsalo.
    # 2) Si no, intenta BUSCAR por email y, si aparece, úsalo.
    # 3) Si no aparece, NO lo crees aquí. Pasa customer_email y Stripe lo crea SOLO si se completa el checkout.
    customer_param = {}
    existing_customer_id = getattr(current_user, "stripe_customer_id", None)

    if existing_customer_id:
        customer_param = {"customer": existing_customer_id}
    else:
        # Intentar recuperar uno existente por email (sin crear)
        found_id = None
        try:
            res = stripe.Customer.search(query=f"email:'{current_user.email}'", limit=5)
            if res.data:
                found_id = res.data[0].id
        except Exception:
            pass

        if found_id:
            customer_param = {"customer": found_id}
        else:
            customer_param = {"customer_email": current_user.email}  # <- NO crea hasta completar
    # --------------------------------------------------------------------

    # Preparar kwargs base para Stripe Checkout (sin allow_promotion_codes aún)
    session_kwargs = {
        "payment_method_types": ['card'],  # opcional
        "mode": "subscription",
        "line_items": [{"price": price_id, "quantity": 1}],
        "subscription_data": subscription_data,
        **customer_param,  # usa customer O customer_email (no ambos)
        "success_url": url_for("payment_success_stripe", _external=True) + "?session_id={CHECKOUT_SESSION_ID}",
        "cancel_url": url_for("stripe_payment", _external=True),
        "metadata": {
            "user_id": str(current_user.id),
            "plan_type": plan_type,
            "referrer_code": referrer_code,
            "youtuber_id": (str(y.id) if y else "")
        },
        "client_reference_id": str(current_user.id),
    }

    # Intentar aplicar descuento por referido
    discounts_applied = False
    if y and y.stripe_coupon_id:
        try:
            promo_codes = stripe.PromotionCode.list(
                coupon=y.stripe_coupon_id,
                active=True,
                limit=1
            )
            if promo_codes.data:
                promotion_code_id = promo_codes.data[0].id
            else:
                promo = stripe.PromotionCode.create(coupon=y.stripe_coupon_id)
                promotion_code_id = promo.id

            session_kwargs["discounts"] = [{"promotion_code": promotion_code_id}]
            discounts_applied = True
        except Exception as e:
            print(f"[checkout] promo apply failed (pre-create): {e} — continuing without discount")

    # Si NO hay descuentos, habilitamos allow_promotion_codes (para cupones manuales en la UI)
    if not discounts_applied:
        session_kwargs["allow_promotion_codes"] = True

    # Crear la sesión de Stripe Checkout (con fallback si falla por descuentos)
    try:
        session = stripe.checkout.Session.create(**session_kwargs)
        return jsonify(id=session.id, sessionId=session.id), 200
    except Exception as e:
        print(f">> Stripe error (first attempt): {e}")

        # Si el primer intento tenía discounts, reintentamos sin 'discounts'
        if discounts_applied and "discounts" in session_kwargs:
            try:
                sk2 = dict(session_kwargs)
                sk2.pop("discounts", None)
                sk2["allow_promotion_codes"] = True  # ya sin discounts, permitimos cupones manuales
                print("[checkout] retrying Session.create WITHOUT discounts…")
                session = stripe.checkout.Session.create(**sk2)
                return jsonify(id=session.id, sessionId=session.id), 200
            except Exception as e2:
                print(f">> Stripe error (retry without discounts): {e2}")
                return jsonify(error=str(e2)), 400

        return jsonify(error=str(e)), 400






@app.route("/payment_success_stripe")
@login_required
def payment_success_stripe():
    session_id = request.args.get('session_id')

    if not session_id:
        flash(_("No session ID found."), "danger")
        return redirect(url_for("index"))

    try:
        # Recupera la sesión y expande line_items y subscripción
        session = stripe.checkout.Session.retrieve(
            session_id,
            expand=['line_items', 'subscription']
        )

        subscription_id = session.subscription.id

        # Accede al objeto 'price' desde los line_items
        price_data = session['line_items']['data'][0]['price']
        price_id = price_data['id']
        metadata = price_data.get("metadata", {})
        membership_type = metadata.get("plan_tipo", "desconocido")

        # Guarda en base de datos
        if current_user.is_authenticated:
            current_user.is_member = True
            current_user.subscription_id = subscription_id
            current_user.membership_type = membership_type
            db.session.commit()

        debug_log(f"[payment_success] user_id={current_user.id} plan={membership_type} price_id={price_id}")

        flash(_("Pago realizado con éxito. ¡Bienvenido a BackTestingMarket!"), "success")
        return render_template('payment_success.html', subscription_id=subscription_id)

    except Exception as e:
        error_log(f"[payment_success_stripe] Error: {e}")
        flash(_("Hubo un error al procesar tu pago."), "danger")
        return redirect(url_for("index"))


@app.route("/billing-portal", methods=["POST"])
@login_required
def billing_portal():
    # 1) Prioriza el customer guardado
    customer_id = getattr(current_user, "stripe_customer_id", None)

    # 2) Si no lo tienes, intenta derivarlo desde una sub previa (activa o cancelada)
    if not customer_id:
        sub_id = getattr(current_user, "subscription_id", None)
        if sub_id:
            sub = stripe.Subscription.retrieve(sub_id, expand=["customer"])
            customer_id = sub.customer.id if hasattr(sub.customer, "id") else sub.customer
            try:
                current_user.stripe_customer_id = customer_id
                db.session.commit()
            except Exception:
                db.session.rollback()

    # 3) Si aún no hay customer, nunca hizo checkout → envíalo a elegir plan
    if not customer_id:
        flash(_("Aún no tienes un cliente en Stripe. Elige un plan para comenzar."), "warning")
        return redirect(url_for("stripe_payment"))

    # 4) Crear la sesión del portal (no crea Customers nuevos)
    portal = stripe.billing_portal.Session.create(
        customer=customer_id,
        return_url=url_for("home", _external=True)
    )
    return redirect(portal.url, code=303)


AGREEMENT_VERSION = "v1.3-2025-10-17"  # súbelo si cambias el texto


def _create_account_link(y):
    return stripe.AccountLink.create(
        account=y.stripe_account_id,
        # Fuerza HTTPS en ambas URLs
        refresh_url=url_for("onboarding_start", code=y.referrer_code, _external=True, _scheme="https"),
        return_url=url_for("onboarding_status", code=y.referrer_code, _external=True, _scheme="https"),
        type="account_onboarding",
    )


@csrf.exempt
@app.route("/onboarding/start/<code>", methods=["GET", "POST"])
def onboarding_start(code):
    y = Youtuber.query.filter_by(referrer_code=code).first()
    if not y:
        return "No existe youtuber con ese code (revisa DB)", 404
    if not y.stripe_account_id:
        return "Youtuber sin stripe_account_id", 400

    # POST: aceptó el acuerdo → registrar (opcional) y redirigir a Stripe
    if request.method == "POST":
        if not request.form.get("agree"):
            return "Debes aceptar los Términos para continuar.", 400

        # (OPCIONAL) Espejar aceptación en metadata de Stripe. Si falla, no bloquea.
        try:
            ip = request.headers.get("X-Forwarded-For", request.remote_addr)
            ts_iso = datetime.now(timezone.utc).isoformat()
            v = request.form.get("agreement_version") or AGREEMENT_VERSION
            stripe.Account.modify(
                y.stripe_account_id,
                metadata={
                    "affiliate_agreement_accepted": "true",
                    "affiliate_agreement_version": v,
                    "affiliate_agreed_at": ts_iso,
                    "affiliate_agreed_ip": ip,
                }
            )
        except Exception:
            app.logger.warning("[onboarding] No se pudo guardar metadata en Stripe", exc_info=True)

        try:
            link = _create_account_link(y)
            return redirect(link.url)
        except Exception as e:
            app.logger.exception("Error creando Account Link de Stripe")
            return f"Error creando Account Link: {e}", 500

    # GET: mostrar acuerdo
    return render_template("onboarding_agreement.html",
                           code=code,
                           agreement_version=AGREEMENT_VERSION)


@app.route("/onboarding/refresh/<code>", methods=["GET"])
def onboarding_refresh(code):
    # Stripe redirige aquí si el link caducó o ya fue usado.
    return onboarding_start(code)


@app.route("/onboarding/status/<code>", methods=["GET"])
def onboarding_status(code):
    """Check in Stripe if onboarding is actually completed."""
    y = Youtuber.query.filter_by(referrer_code=code).first()
    if not y or not y.stripe_account_id:
        return "Creator not found or without Stripe account.", 404

    acct = stripe.Account.retrieve(y.stripe_account_id)
    details_submitted = acct.get("details_submitted", False)
    payouts_enabled = acct.get("payouts_enabled", False)
    charges_enabled = acct.get("charges_enabled", False)
    reqs = acct.get("requirements", {}) or {}
    currently_due = reqs.get("currently_due", [])
    past_due = reqs.get("past_due", [])
    eventually_due = reqs.get("eventually_due", [])

    completed = payouts_enabled and details_submitted and not currently_due

    html = """
    <div style="font-family:system-ui, -apple-system, Segoe UI, Roboto; max-width: 760px; margin: 32px auto; padding: 16px;">
      <h2 style="margin-bottom: 12px;">Onboarding Status</h2>

      {% if completed %}
        <p>✅ <strong>All set!</strong> Your Stripe account is ready to receive payouts.</p>
        <ul>
          <li><code>details_submitted</code>: {{ details_submitted }}</li>
          <li><code>payouts_enabled</code>: {{ payouts_enabled }}</li>
          <li><code>charges_enabled</code>: {{ charges_enabled }}</li>
        </ul>
      {% else %}
        <p>⚠️ <strong>Action required.</strong> Your onboarding is not finished yet.</p>
        <p><strong>Pending requirements (<code>currently_due</code>):</strong></p>
        <pre style="background:#f6f8fa; padding:12px; border-radius:6px; overflow:auto;">{{ currently_due or "—" }}</pre>

        {% if past_due %}
          <p><strong>Past due:</strong></p>
          <pre style="background:#f6f8fa; padding:12px; border-radius:6px; overflow:auto;">{{ past_due }}</pre>
        {% endif %}

        {% if eventually_due %}
          <p><strong>Eventually due:</strong></p>
          <pre style="background:#f6f8fa; padding:12px; border-radius:6px; overflow:auto;">{{ eventually_due }}</pre>
        {% endif %}

        <p style="margin-top: 12px;">
          <a href="{{ url_for('onboarding_start', code=code, _external=True) }}">Resume onboarding on Stripe</a>
        </p>
        <small style="color:#666;">If you closed the window or the link expired, click “Resume onboarding” again to get a fresh link.</small>
      {% endif %}

      <hr style="margin:24px 0;">
      <p style="color:#666; font-size: 14px;">
        Having trouble? Reply to this email or contact support and include your referral code: <code>{{ code }}</code>.
      </p>
    </div>
    """

    return render_template_string(
        html,
        completed=completed,
        details_submitted=details_submitted,
        payouts_enabled=payouts_enabled,
        charges_enabled=charges_enabled,
        currently_due=currently_due,
        past_due=past_due,
        eventually_due=eventually_due,
        code=code,
    )


@app.route("/onboarding/success/<code>", methods=["GET"])
def onboarding_success(code):
    return redirect(url_for("onboarding_status", code=code))


# Fallbacks globales si NO hay ref o el youtuber no tiene price_id propio
DEFAULT_MONTHLY_PRICE_ID = os.getenv("STRIPE_PRICE_MONTHLY_DEFAULT", "price_1RjuuYEiGxFFi4pPy361fuee")
DEFAULT_ANNUAL_PRICE_ID = os.getenv("STRIPE_PRICE_ANNUAL_DEFAULT",  "price_1RjuuYEiGxFFi4pPXIh1pcNx")


@csrf.exempt
@app.post("/api/get-prices")
def api_get_prices():
    body = request.get_json(silent=True) or {}
    ref_code = (getattr(current_user, "referrer_code", None) or body.get("ref_code") or "").strip()
    print(ref_code)
    # Precios base desde Stripe
    try:
        base_monthly = stripe.Price.retrieve(DEFAULT_MONTHLY_PRICE_ID)
        base_annual = stripe.Price.retrieve(DEFAULT_ANNUAL_PRICE_ID)
        monthly_price = int(base_monthly.get("unit_amount", 0))
        annual_price = int(base_annual.get("unit_amount", 0))
    except Exception as e:
        print("Error al obtener precios base:", e)
        return jsonify({
            "monthly": {"amount_cents": 3999, "discounted": False},
            "annual": {"amount_cents": 39999, "discounted": False},
            "error": "base_price_error"
        }), 200

    discount_percent = 0
    coupon_id = None

    if ref_code:
        youtuber = Youtuber.query.filter_by(referrer_code=ref_code).first()
        if youtuber and youtuber.stripe_coupon_id:
            coupon_id = youtuber.stripe_coupon_id

    if coupon_id:
        try:
            coupon = stripe.Coupon.retrieve(coupon_id)
            discount_percent = int(coupon.get("percent_off", 0))
        except Exception as e:
            print("Error al obtener cupón:", e)

    # Aplicar descuento si hay porcentaje válido (> 0)
    discounted_monthly = monthly_price
    discounted_annual = annual_price
    monthly_discounted = False
    annual_discounted = False

    if discount_percent > 0:
        discounted_monthly = int(monthly_price * (100 - discount_percent) / 100)
        discounted_annual = int(annual_price * (100 - discount_percent) / 100)
        monthly_discounted = True
        annual_discounted = True

    return jsonify({
        "monthly": {
            "amount_cents": discounted_monthly,
            "currency": base_monthly.get("currency", "usd"),
            "price_id": base_monthly.get("id"),
            "discounted": monthly_discounted
        },
        "annual": {
            "amount_cents": discounted_annual,
            "currency": base_annual.get("currency", "usd"),
            "price_id": base_annual.get("id"),
            "discounted": annual_discounted
        },
        "ref_used": ref_code
    })


LOG_DIR = "/var/www/html/flask_project/logs2"
CSV_PATH = os.path.join(LOG_DIR, "stripe_payments.csv")
DEBUG_TXT = os.path.join(LOG_DIR, "webhook_debug.txt")
ERROR_TXT = os.path.join(LOG_DIR, "webhook_errors.txt")

PAYMENT_HEADERS = [
    "event_id", "livemode", "event_type", "event_created_utc",
    "invoice_id", "subscription_id", "customer_email",
    "amount_paid_cents", "amount_subtotal_cents", "currency",
    "hosted_invoice_url", "referrer_code", "youtuber_id"
]


def _ensure_dir():
    os.makedirs(LOG_DIR, exist_ok=True)


def debug_log(msg: str):
    """Append de mensajes de depuración."""
    try:
        _ensure_dir()
        with open(DEBUG_TXT, "a", encoding="utf-8") as f:
            f.write(f"{datetime.now(timezone.utc).isoformat()} | {msg}\n")
    except Exception:
        pass


def error_log(msg: str):
    """Append de errores con stacktrace."""
    try:
        _ensure_dir()
        with open(ERROR_TXT, "a", encoding="utf-8") as f:
            f.write(f"{datetime.now(timezone.utc).isoformat()} | {msg}\n")
            f.write(traceback.format_exc())
            f.write("\n")
    except Exception as log_error:
        print("⚠️ ERROR escribiendo en webhook_errors.txt:", log_error)
        import sys
        print("Original error message:", msg, file=sys.stderr)


def write_payment_row(row: dict):
    """Escribe una fila en CSV; crea carpeta/archivo y header si no existen."""
    _ensure_dir()
    file_exists = os.path.exists(CSV_PATH)
    data = {k: row.get(k, "") for k in PAYMENT_HEADERS}

    try:
        with open(CSV_PATH, "a", newline="", encoding="utf-8") as f:
            w = csv.DictWriter(f, fieldnames=PAYMENT_HEADERS)
            if not file_exists:
                w.writeheader()
            w.writerow(data)
        debug_log(f"CSV write OK invoice_id={data['invoice_id']} event_id={data['event_id']}")
    except Exception as e:
        error_log(f"CSV write ERROR: {e}")
        raise


LOG_DIR = "/var/www/html/flask_project/logs2"
TEST_TXT = os.path.join(LOG_DIR, "test.txt")


@csrf.exempt
@app.post("/stripe/webhook")
def stripe_webhook():
    debug_log(f"HIT /stripe/webhook | len_body={len(request.data)} | has_sig={'Stripe-Signature' in request.headers}")

    sig = request.headers.get("Stripe-Signature")
    if not sig:
        debug_log("No Stripe-Signature header -> 400")
        return "", 400

    try:
        event = stripe.Webhook.construct_event(
            request.get_data(cache=False, as_text=False),  # <- usa esto
            sig,
            os.getenv("STRIPE_WEBHOOK_SECRET")
        )

    except stripe.error.SignatureVerificationError as e:
        error_log(f"SignatureVerificationError: {e}")
        return "", 400
    except Exception as e:
        error_log(f"construct_event ERROR: {e}")
        return "", 400

    debug_log(f"Event OK type={event.get('type')} id={event.get('id')} livemode={event.get('livemode')}")

    # Filtro de entorno (opcional)
    try:
        IS_LIVE = bool(int(os.getenv("STRIPE_LIVE_MODE", "0")))
    except Exception:
        IS_LIVE = False

    if IS_LIVE and not event.get("livemode"):
        debug_log("Dropped: live-only webhook received test event")
        return "", 200
    if (not IS_LIVE) and event.get("livemode"):
        debug_log("Dropped: test-only webhook received live event")
        return "", 200

    if event["type"] == "invoice.paid":
        try:
            inv = event["data"]["object"]

            invoice_id = inv.get("id")
            subscription_id = inv.get("subscription")
            currency = inv.get("currency", "usd")
            amount_paid_cents = int(inv.get("amount_paid") or 0)
            amount_subtotal_cents = int(inv.get("amount_subtotal") or 0)
            hosted_url = inv.get("hosted_invoice_url") or ""
            billing_reason = inv.get("billing_reason") or ""
            customer_name = inv.get("customer_name") or ""

            # Email del cliente
            customer_email = inv.get("customer_email") or ""
            if not customer_email:
                try:
                    cust_id = inv.get("customer")
                    if cust_id:
                        cust = stripe.Customer.retrieve(cust_id)
                        customer_email = (cust.get("email") or "").strip()
                except Exception as e:
                    debug_log(f"Customer retrieve failed: {e}")

            # Datos de la suscripción (metadata, status)
            referrer_code = ""
            youtuber_id = ""
            subscription_status = ""
            welcome_already_sent = False
            try:
                if subscription_id:
                    sub = stripe.Subscription.retrieve(subscription_id)
                    subscription_status = sub.get("status", "")  # e.g. 'trialing', 'active', 'past_due'
                    meta = sub.get("metadata") or {}
                    referrer_code = meta.get("referrer_code", "")
                    youtuber_id = meta.get("youtuber_id", "")
                    welcome_already_sent = (meta.get("welcome_sent") == "1")
            except Exception as e:
                debug_log(f"Subscription retrieve failed: {e}")

            # Detecta invoice $0 por trial al crear la suscripción
            is_trial_zero_invoice = (
                amount_paid_cents == 0 and
                billing_reason == "subscription_create" and
                subscription_status == "trialing"
            )
            is_paid_invoice = amount_paid_cents > 0


            # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
            # >>> START: Pago automático de comisión al youtuber (lee metadata de la SUSCRIPCIÓN)

            if (not is_trial_zero_invoice) and is_paid_invoice:

                referred_user_email = (customer_email or "").strip()

                try:
                    # --------------------------------------------------------
                    # 0) Traer invoice expandido con líneas (pero sin expandir subscription)
                    # --------------------------------------------------------
                    inv_full = stripe.Invoice.retrieve(
                        invoice_id,
                        expand=["lines.data.price"]
                    )

                    # ============================================================
                    # 1) Recuperar subscription_id de forma ROBUSTA
                    # ============================================================

                    # Método 1: campo normal
                    subscription_id = inv_full.get("subscription")

                    # Método 2: buscar en invoice.lines
                    if not subscription_id:
                        lines_for_sub = (inv_full.get("lines") or {}).get("data", [])
                        for li in lines_for_sub:

                            # Caso normal
                            if li.get("subscription"):
                                subscription_id = li["subscription"]
                                break

                            # Caso Stripe 2024–2025
                            parent = li.get("parent", {}).get("subscription_item_details", {})
                            if parent.get("subscription"):
                                subscription_id = parent["subscription"]
                                break

                    # Método 3: invoice.parent.subscription_details
                    if not subscription_id:
                        subscription_id = (
                            inv_full.get("parent", {})
                                .get("subscription_details", {})
                                .get("subscription")
                        )

                    debug_log(f"[affiliate] subscription_id detectado = {subscription_id}")

                    # ============================================================
                    # 2) LEER METADATA DE LA SUSCRIPCIÓN
                    # ============================================================
                    sub_meta = {}
                    if subscription_id:
                        try:
                            sub_obj = stripe.Subscription.retrieve(subscription_id)
                            if isinstance(sub_obj, dict):
                                sub_meta = sub_obj.get("metadata") or {}
                        except Exception as e:
                            debug_log(f"[affiliate] error leyendo metadata de suscripción: {e}")

                    # Rellenar campos desde metadata
                    youtuber_id   = sub_meta.get("youtuber_id", youtuber_id)
                    referrer_code = sub_meta.get("referrer_code", referrer_code)
                    meta          = sub_meta

                    debug_log(f"[affiliate] METADATA → {sub_meta}")

                    # ============================================================
                    # 3) Resolver afiliado en tu DB
                    # ============================================================
                    yt, destination_acct, pct = _find_affiliate_in_db(youtuber_id, referrer_code)

                    if destination_acct:

                        # --------------------------------------------------------
                        # 4) Base de comisión = PRECIO DE LISTA (unit_amount)
                        # --------------------------------------------------------
                        list_price_cents = None

                        try:
                            lines = (inv_full.get("lines", {}) or {}).get("data", [])

                            for li in lines:

                                # ----------- DETECTOR UNIVERSAL (2024–2025) -----------
                                # Detectar si esta línea pertenece a la suscripción
                                parent = li.get("parent", {}).get("subscription_item_details", {})
                                if parent.get("subscription") != subscription_id:
                                    continue

                                # -------------- A) nuevo formato Stripe --------------
                                pricing = li.get("pricing") or {}
                                ua_dec = pricing.get("unit_amount_decimal")
                                if ua_dec:
                                    try:
                                        list_price_cents = int(float(ua_dec))
                                        break
                                    except:
                                        pass

                                # -------------- B) formato antiguo --------------
                                price = li.get("price") or {}
                                ua_old = price.get("unit_amount")
                                if ua_old is not None:
                                    list_price_cents = int(ua_old)
                                    break

                        except Exception as e:
                            debug_log(f"[affiliate] error leyendo precios: {e}")

                        # -------------- C) Fallback si todo falla --------------
                        if not list_price_cents:
                            list_price_cents = int(inv_full.get("amount_subtotal") or 0)

                        base_cents = list_price_cents
                        commission_cents = int(Decimal(base_cents) * pct)

                        # --------------------------------------------------------
                        # 5) Ejecutar Transfer
                        # --------------------------------------------------------
                        result = pay_affiliate_commission(
                            invoice_id=invoice_id,
                            subscription_id=subscription_id,
                            destination_acct=destination_acct,
                            commission_cents=commission_cents,
                            currency=currency,
                            pct=pct,
                            base_cents=base_cents,
                            youtuber_id=(yt.id if yt else None),
                            referrer_code=referrer_code,
                            plan_type=meta.get("plan_type") if 'meta' in locals() else None,
                            referred_user_email=referred_user_email,

                        )

                        # --------------------------------------------------------
                        # 6) Logs
                        # --------------------------------------------------------
                        if result["status"] == "ok":
                            debug_log(f"[affiliate] transfer OK id={result['transfer_id']} amount={result['amount_cents']}c dest={destination_acct}")
                        elif result["status"] == "accrue":
                            debug_log(f"[affiliate] below min threshold: {commission_cents}c < {result.get('threshold_cents')}c (no transfer)")
                        else:
                            error_log(f"[affiliate] transfer ERROR: {result.get('reason')} {result.get('error')}")

                    else:
                        debug_log(
                            f"[affiliate] No affiliate for invoice={invoice_id} "
                            f"ref={referrer_code} yt_id={youtuber_id}"
                        )

                except Exception as e:
                    error_log(f"[affiliate] unexpected ERROR: {e}")

            # <<< END: Pago automático de comisión al youtuber
            # <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

            # Enviar email de bienvenida SOLO una vez (y marcar metadata)
            if is_trial_zero_invoice and not welcome_already_sent and customer_email:
                try:
                    # Detectar idioma preferido desde Stripe Customer
                    locale = "en"
                    try:
                        cust_id = inv.get("customer")
                        if cust_id:
                            cust = stripe.Customer.retrieve(cust_id)
                            locales = cust.get("preferred_locales") or []
                            if locales:
                                locale = locales[0][:2].lower()
                    except Exception as e:
                        debug_log(f"Locale detection failed: {e}")

                    if locale == "en":
                        subject = "Welcome to BacktestingMarket!"
                        body = f"""Hi {customer_name},

            Thank you for joining our community of traders who seek to make informed decisions backed by real data.

            🧠 What can you expect?
            Your free trial is now active, and you can explore everything our platform has to offer:
            • Vertical and Iron Condor Idea Generation
            • Backtesting
            • Key metrics and clear visualizations
            • And much more!

            We have a completely free WhatsApp group for subscribers. If you’d like to join, send me your country code and phone number so I can add you.

            In this group, we share trading ideas, strategies, alerts, platform updates, and much more — all in real time and in a collaborative environment among subscribers.

            ⏱ In the meantime…

            Feel free to explore and get familiar with the interface. If you have any questions, you can write to us anytime. We’re here to help.

            Welcome again, and much success in your trading!

            The BacktestingMarket Team
            """
                    else:
                        subject = "¡Bienvenido a BacktestingMarket!"
                        body = f"""Hola {customer_name},

            Gracias por unirte a nuestra comunidad de traders que buscan tomar decisiones informadas y respaldadas por datos reales.

            🧠 ¿Qué puedes esperar?
            Ya puedes comenzar tu prueba gratuita y explorar todo lo que nuestra plataforma tiene para ofrecer:
            • Generación de Ideas de Verticales y Iron Condors
            • Backtesting
            • Métricas clave y visualizaciones claras
            • ¡Y mucho más!

            Tenemos un grupo de WhatsApp totalmente gratuito para suscriptores. Si deseas participar, envíame tu código de país y teléfono para agregarte.

            En este grupo compartimos ideas de trading, estrategias, alertas, novedades de la plataforma y mucho más — todo en tiempo real y en un entorno colaborativo entre suscriptores.

            ⏱ Mientras tanto…

            Te invitamos a explorar y familiarizarte con la interfaz. Cualquier duda que tengas, puedes escribirnos cuando quieras. Estamos aquí para ayudarte.

            ¡Bienvenido de nuevo y mucho éxito con tu trading!

            El equipo de
            BacktestingMarket
            """

                    # Enviar email
                    send_email_general(
                        subject=subject,
                        sender_name="BacktestingMarket",
                        sender_email="info@backtestingmarket.com",
                        receiver_email=customer_email,
                        body=body
                    )
                    debug_log(f"Welcome email sent to {customer_email} (lang={locale}, subscription_id={subscription_id})")

                    # Marca metadata para no reenviar por reintentos
                    try:
                        stripe.Subscription.modify(subscription_id, metadata={"welcome_sent": "1"})
                    except Exception as e:
                        error_log(f"Could not set welcome_sent metadata: {e}")

                except Exception as e:
                    error_log(f"Welcome email ERROR: {e}")

            # Registrar SIEMPRE en CSV (pago $0 o >$0)
            row = {
                "event_id": event["id"],
                "livemode": event.get("livemode", False),
                "event_type": event["type"],
                "event_created_utc": datetime.fromtimestamp(
                    event["created"], tz=timezone.utc
                ).isoformat(),
                "invoice_id": invoice_id,
                "subscription_id": subscription_id,
                "customer_email": customer_email,
                "amount_paid_cents": amount_paid_cents,
                "amount_subtotal_cents": amount_subtotal_cents,
                "currency": currency,
                "hosted_invoice_url": hosted_url,
                "referrer_code": referrer_code,
                "youtuber_id": youtuber_id,
                "billing_reason": billing_reason,
                "subscription_status": subscription_status,
                "is_trial": bool(is_trial_zero_invoice),
                "is_paid": bool(is_paid_invoice),
            }
    

            # --------------------------------------------------------
            # ACTUALIZAR membership_type EN BASE DE DATOS
            # --------------------------------------------------------
            try:
                user = User.query.filter_by(subscription_id=subscription_id).first()
                if user:
                    # Obtener el price_id actual de Stripe
                    sub_obj = stripe.Subscription.retrieve(subscription_id, expand=["items.data.price"])
                    items = sub_obj.get("items", {}).get("data", [])
                    if items:
                        price_id = items[0]["price"]["id"]

                        # Traduce price_id a membership_type
                        new_type = None
                        if "anual" in price_id.lower() or "annual" in price_id.lower():
                            new_type = "anual"
                        elif "mensual" in price_id.lower() or "monthly" in price_id.lower():
                            new_type = "mensual"

                        # Actualizar si cambió
                        if new_type and user.membership_type != new_type:
                            debug_log(f"[membership] Cambio detectado user_id={user.id}: {user.membership_type} → {new_type}")
                            user.membership_type = new_type
                            db.session.commit()
                        else:
                            debug_log(f"[membership] Sin cambios para user_id={user.id}")

            except Exception as e:
                error_log(f"[membership] ERROR actualizando membership_type: {e}")


            write_payment_row(row)
        except Exception as e:
            error_log(f"invoice.paid handler ERROR: {e}")
            # Devolvemos 200 para evitar reintentos infinitos por parte de Stripe
            return "", 200

    if event["type"] in {"customer.subscription.updated", "customer.subscription.deleted"}:
        try:
            sub = event["data"]["object"]
            sub_id = sub.get("id")
            status = sub.get("status")  # "active", "canceled", etc.
            cancel_at_period_end = bool(sub.get("cancel_at_period_end"))
            current_period_end = sub.get("current_period_end")
            canceled_at = sub.get("canceled_at")
            cancellation_reason = ""
            cancellation_details = sub.get("cancellation_details") or {}
            if cancellation_details:
                cancellation_reason = (
                    cancellation_details.get("feedback") or cancellation_details.get("comment") or ""
                )

            # Buscar email del cliente
            email = ""
            name = ""
            try:
                cust_id = sub.get("customer")
                if cust_id:
                    cust = stripe.Customer.retrieve(cust_id)
                    email = (cust.get("email") or "").strip()
                    name = (cust.get("name") or "").strip()
            except Exception as e:
                debug_log(f"Customer retrieve failed for cancel event: {e}")

            # Buscar referrer info (si aún disponible)
            referrer_code = ""
            youtuber_id = ""
            try:
                meta = sub.get("metadata") or {}
                referrer_code = meta.get("referrer_code", "")
                youtuber_id = meta.get("youtuber_id", "")
            except Exception:
                pass

            # Guardar en CSV (como log histórico)
            row = {
                "event_id": event["id"],
                "livemode": event.get("livemode", False),
                "event_type": event["type"],
                "event_created_utc": datetime.fromtimestamp(event["created"], tz=timezone.utc).isoformat(),
                "invoice_id": None,
                "subscription_id": sub_id,
                "customer_email": email,
                "amount_paid_cents": None,
                "amount_subtotal_cents": None,
                "currency": None,
                "hosted_invoice_url": None,
                "referrer_code": referrer_code,
                "youtuber_id": youtuber_id,
                "billing_reason": None,
                "subscription_status": status,
                "is_trial": None,
                "is_paid": None,
                "customer_name": name,
                "cancel_at_period_end": cancel_at_period_end,
                "cancellation_reason": cancellation_reason,
                "canceled_at": (
                    datetime.fromtimestamp(canceled_at, tz=timezone.utc).isoformat() if canceled_at else None
                ),
            }
            write_payment_row(row)

            # Lógica de baja efectiva
            user = User.query.filter_by(subscription_id=sub_id).first()
            if not user:
                debug_log(f"[cancel] user not found sub_id={sub_id}")
                return "", 200

            if event["type"] == "customer.subscription.deleted" or status == "canceled":
                user.is_member = False
                user.membership_type = None
                db.session.commit()
                debug_log(f"[cancel] Baja efectiva user_id={user.id} sub_id={sub_id}")
            else:
                debug_log(f"[cancel] No baja aún — status={status}, sub_id={sub_id}")

        except Exception as e:
            error_log(f"[cancel handler ERROR] {e}")
        return "", 200
    return "", 200

def _find_affiliate_in_db(youtuber_id, referrer_code):
    """
    Devuelve (yt, stripe_account_id, commission_percent_decimal) o (None, None, None)
    """
    try:
        with app.app_context():
            yt = None
            if youtuber_id:
                yt = Youtuber.query.filter_by(id=youtuber_id).first()
            if (not yt) and referrer_code:
                yt = Youtuber.query.filter_by(referrer_code=referrer_code).first()
            if yt and yt.stripe_account_id:
                # commission_percent guardado como 10.0 = 10%
                pct = Decimal(str(yt.commission_percent or 10.0)) / Decimal("100")
                return yt, yt.stripe_account_id, pct
    except Exception as e:
        error_log(f"[affiliate] DB lookup error: {e}")
    return None, None, None

MIN_PAYOUT_CENTS = int(os.getenv("AFFILIATE_MIN_PAYOUT_CENTS", "0"))  # 0 = sin mínimo

def pay_affiliate_commission(
    *,
    invoice_id: str,
    subscription_id: str,
    destination_acct: str,   # acct_xxx del youtuber
    commission_cents: int,
    currency: str = "usd",
    pct: Decimal = Decimal("0.10"),
    base_cents: int = 0,
    youtuber_id=None,
    referrer_code: str = "",
    plan_type: str | None = None,
    idem_suffix: str | None = None,
    referred_user_email: str | None = None  
):
    """Hace el Transfer a la connected account. Sin DB; retorna solo status."""
    if commission_cents <= 0:
        return {"status": "skipped", "reason": "zero_commission"}

    if commission_cents < MIN_PAYOUT_CENTS:
        return {"status": "accrue", "reason": "below_min_threshold", "threshold_cents": MIN_PAYOUT_CENTS}

    idem_key = f"aff_transfer_{invoice_id}_{youtuber_id}_{idem_suffix or 'v1'}"
    try:
        tr = stripe.Transfer.create(
            amount=commission_cents,
            currency=(currency or "usd"),
            destination=destination_acct,
            description=f"Affiliate {int(pct*100)}% for invoice {invoice_id}",
            metadata={
                "invoice_id": invoice_id,
                "subscription_id": subscription_id,
                "youtuber_id": str(youtuber_id) if youtuber_id is not None else "",
                "referrer_code": referrer_code or "",
                "base_cents": str(base_cents),
                "commission_cents": str(commission_cents),
                "plan_type": plan_type or "",
                "referred_user_email": referred_user_email or "",  
            },
            idempotency_key=idem_key,
        )
        return {"status": "ok", "transfer_id": tr.id, "amount_cents": commission_cents, "currency": currency or "usd"}
    except Exception as e:
        return {"status": "retry", "reason": "error", "error": str(e)}



ALLOWED_EMAILS = set(e.strip().lower() for e in os.getenv("ADMIN_ALLOWED_EMAILS", "").split(",") if e.strip())


def admin_only(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        # Debe estar logueado y su email debe estar en la whitelist
        if not current_user.is_authenticated or (getattr(current_user, "email", "").lower() not in ALLOWED_EMAILS):
            abort(403)
        return f(*args, **kwargs)
    return wrapper


@app.route("/makeka")
@login_required
@admin_only
def admin_page():
    return render_template("admin.html")


@app.route("/tutorias")
def tutorias_page():
    # puedes pasar timezone si lo guardas en perfil
    return render_template("tutory.html", timezone="Europe/Tirane")


if __name__ == "__main__":
    app.run(debug=True, port=5002)
