from concurrent.futures import ProcessPoolExecutor
import numpy as np
from collections import deque
import os
import glob
import pandas as pd
from flask import Flask, render_template, request, jsonify
from flask import Flask, request, jsonify
from tasks import process_backtesting_task  # Importa la tarea de Celery
from models.entities.User import User
from models.ModelUser import ModelUser
import re
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from flask import Flask, render_template, request, url_for, redirect, flash, session
from flask_bootstrap import Bootstrap
import csv
import funcionesV2
from datetime import datetime
import json
from itertools import zip_longest
from config import config
from flask_mysqldb import MySQL
from flask_login import LoginManager, login_user, logout_user, login_required
from flask_wtf.csrf import CSRFProtect
from flask import jsonify
from config import Config
from flask_compress import Compress
import fear_and_greed
import secrets
from flask_talisman import Talisman
import time
import logging
logging.basicConfig(level=logging.DEBUG)


# para scrapping

# Models

# Entities

app = Flask(__name__)
Compress(app)
app.secret_key = Config.SECRET_KEY
bootstrap = Bootstrap(app)
db = MySQL(app)
login_manager_app = LoginManager(app)
csrf = CSRFProtect()


@login_manager_app.user_loader
def load_user(id):
    return ModelUser.get_by_id(db, id)


# Ruta para el directorio raiz
@app.route('/')
def index():
    # return render_template('/index.html')
    return redirect(url_for('login'))


# Ruta para login!!!
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':

        # token  = request.form.get('token')

        # # Token predefinido
        # token_predefinido = "tokentoken"  # Reemplaza con tu token único

        # # Verificar si el token es válido
        # if token != token_predefinido:
        #     mensaje = "<div style='position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 20px;'><br>Acceso denegado. Este es un link privado. <br>Para acceder a esta ruta solo puede hacerlo desde <a href='http://www.opcionsigma.com'>www.opcionsigma.com</a></div>"
        #     # Retornar el mensaje con el enlace HTML
        #     return mensaje

        # Rutina que chequea si el usuario proviene desde un link de opciosigma.com
        # referer = request.headers.get('Referer')
        # if not referer or 'opcionsigma.com' not in referer:
        #     mensaje = "<div style='position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 20px;'><br>Acceso denegado. Este es un link privado. <br>Para acceder a esta ruta solo puede hacerlo desde <a href='http://www.opcionsigma.com'>www.opcionsigma.com</a></div>"
        #     # Retornar el mensaje con el enlace HTML
        #     return mensaje

        username = 'master@opcionsigma.com'
        password = 'master1234'
        user = User(0, username, password)
        logged_user = ModelUser.login(db, user)

        if logged_user != None:
            if logged_user.password:
                login_user(logged_user)
                session['userID'] = (logged_user.id)
                session['userPlan'] = (logged_user.userPlan)
                # Registra el ID y el timestamp en el archivo CSV
                try:
                    funcionesV2.registrar_log(session['userID'])
                except Exception as e:
                    return jsonify({"error": str(e)})

                return redirect(url_for('dashboard'))
            else:
                flash("Invalid password")
                return render_template('/auth/login.html')
        else:
            flash("User not found")
            return render_template('/auth/login.html')
    else:
        # mensaje = "<div style='position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 20px;'><br>Acceso denegado. Este es un link privado. <br>Para acceder a esta ruta solo puede hacerlo desde <a href='http://www.opcionsigma.com'>www.opcionsigma.com</a></div>"
        # return mensaje

        return render_template('/auth/login.html')


# Funcion para obtener y cargar los valores del profile de cada usuario!!!
@app.route('/get_modal_data')
@login_required
def get_modal_data():
    settingData = ModelUser.get_by_id(db, session['userID'])
    data = vars(settingData)
    return jsonify(data)


@app.route('/dashboard')
@login_required
def dashboard():
    return render_template('/dashboard.html', is_dashboard=True)


@app.route('/dashboardV2')
def dashboardV2():
    return render_template('/dashboardV2.html', is_dashboard=True)


@app.route('/heatmap')
@login_required
def heatmap():
    if 'userPlan' in session and session['userPlan'] == 1:
        return redirect(url_for('login'))
    else:
        return render_template('/heatmap.html')


@app.route('/alerts')
@login_required
def alerts():
    if 'userPlan' in session and session['userPlan'] == 1:
        return redirect(url_for('login'))
    else:
        return render_template('/alerts.html')


@app.route('/chart_live')
@login_required
def chart_live():
    if 'userPlan' in session and session['userPlan'] == 1:
        return redirect(url_for('login'))
    else:
        return render_template('/chart_live.html')


@app.route('/statistics')
@login_required
def statistics():
    return render_template('/statistics.html')


@app.route('/ostv')
@login_required
def ostv():
    return render_template('/ostv.html')


@app.route('/backtesting')
@login_required
def backtesting():
    return render_template('/backtesting.html')

# Funcion para obtener los dias que el mercado estuvo abierto y/o si cada estrategia tiene datos!!!


@app.route('/get_label_data/<string:EstrategiaNumber>')
@login_required
def get_label_data(EstrategiaNumber):
    diasHabilitados = funcionesV2.obtener_Dias(EstrategiaNumber)
    return jsonify({
        'diasHabilitados': diasHabilitados
    })

# Definir funciones para calcular los datos necesarios


def calcular_datos_grafico(estrategia, offset):
    data = funcionesV2.listaParaGraficoDiario(estrategia, offset)
    x_data = data.iloc[:, 0].tolist()
    y_data = data.iloc[:, 4].tolist()
    ultimoPrecioSPX = "{:.2f}".format(data.iloc[-1, 2])
    profitLossPercentage = "{:.2f}".format(data.iloc[-1, 4] * 100)
    profitLossDollar = "{:.2f}".format(data.iloc[-1, 3])
    return x_data, y_data, ultimoPrecioSPX, profitLossPercentage, profitLossDollar


@app.route('/chart_live/<estrategia>/<takeProfit>/<stopLoss>/<offset>/<dollarOrPerc>', methods=['GET'])
@login_required
def mostrar_chart_live(estrategia, takeProfit, stopLoss, offset, dollarOrPerc):
    x_data = []
    y_data = []
    z_data = []
    w_data = []
    data, cabecera = funcionesV2.listaParaGraficoDiario(estrategia, offset)
    x_data = data.iloc[:, 0].tolist()
    if dollarOrPerc == 'P':
        y_data = data.iloc[:, 6].tolist()  # Columna 6 tiene los porcentanjes de movimiento
    else:
        y_data = data.iloc[:, 5].tolist()  # Columa 5 tiene los precios de movimiento

    z_data = data.iloc[:, 4].tolist()  # Columna 4 tiene el EM
    u_data = data.iloc[:, 3].tolist()  # Columna 3 tiene el VIX
    w_data = data.iloc[:, 2].tolist()  # Columna 2 tiene el SPX
    ultimoPrecioSPX = "{:.2f}".format(data.iloc[-1, 2])
    profitLossPercentage = "{:.2f}".format(data.iloc[-1, 6]*100)  # Columna 6 tiene los porcentanjes de movimiento
    profitLossDollar = "{:.2f}".format(data.iloc[-1, 5])  # Columa 5 tiene los precios de movimiento

    credit = float(data.iloc[0, 1]) - float(offset)
    primerSPX = data.iloc[0, 2]
    ModDif = primerSPX % 5
    creditLess = float(primerSPX)-ModDif
    if ModDif > 2.5:
        strikeSPX = int(creditLess+5)
    else:
        strikeSPX = int(creditLess)

    wing1 = "BTO CALL " + str(cabecera.iloc[0, 2])
    wing2 = "STO CALL " + str(cabecera.iloc[0, 0])
    wing3 = "STO PUT " + str(cabecera.iloc[0, 1])
    wing4 = "BTO PUT " + str(cabecera.iloc[0, 3])

    response_data = {
        'x_data': x_data,
        'y_data': y_data,
        'z_data': z_data,
        'w_data': w_data,
        'strikeSPX': strikeSPX,
        'wing1': wing1,
        'wing2': wing2,
        'wing3': wing3,
        'wing4': wing4,
        'ultimoPrecioSPX': ultimoPrecioSPX,
        'profitLossPercentage': profitLossPercentage,
        'profitLossDollar': profitLossDollar,
        'credit': credit,
    }

    json_response = json.dumps(response_data)
    return json_response


@app.route('/chart_historical')
@login_required
def chart_historical():
    return render_template('/chart_historical.html')


@app.route('/indicadores')
@login_required
def indicadores():
    return render_template('/indicadores.html')


@app.route('/chart_historical/<fecha>/<estrategia>/<takeProfit>/<stopLoss>/<offset>/<dollarOrPerc>', methods=['GET'])
@login_required
def mostrar_chart(fecha, estrategia, takeProfit, stopLoss, offset, dollarOrPerc):
    x_data = []
    y_data = []
    # z_data= []
    data, cabecera = funcionesV2.listaParaGrafico(fecha, estrategia, float(offset))
    x_data = data.iloc[:, 0].tolist()
    # z_data = data.iloc[:, 3].tolist()

    # price 1
    # spx   2
    # vix   3
    # P/L $ 4
    # P/L % 5

    if dollarOrPerc == 'P':
        y_data = data.iloc[:, 5].tolist()
    else:
        y_data = data.iloc[:, 4].tolist()

    hayNoticias = funcionesV2.verificarFeriados(fecha)
    primerSPX = data.iloc[0, 2]
    credit = float(data.iloc[0, 1]) - float(offset)

    # Generar estructura del IB en base al 1er precio del SPX
    ModDif = primerSPX % 5
    creditLess = float(primerSPX)-ModDif
    if ModDif > 2.5:
        strikeSPX = int(creditLess) + 5
    else:
        strikeSPX = int(creditLess)

    if estrategia == '5':
        anchoAlas = 30
    if estrategia == '6':
        anchoAlas = 50
    if estrategia == '1' or estrategia == '2' or estrategia == '3' or estrategia == '4':
        anchoAlas = 40

    if estrategia == '7' or estrategia == '8':
        wing1 = "BTO CALL " + str(cabecera.iloc[0, 2])
        wing2 = "STO CALL " + str(cabecera.iloc[0, 0])
        wing3 = "STO PUT " + str(cabecera.iloc[0, 1])
        wing4 = "BTO PUT " + str(cabecera.iloc[0, 3])
    else:
        wing1 = "BTO CALL " + str(strikeSPX + anchoAlas)
        wing2 = "STO CALL " + str(strikeSPX)
        wing3 = "STO PUT " + str(strikeSPX)
        wing4 = "BTO PUT " + str(strikeSPX - anchoAlas)

    response_data = {
        'x_data': x_data,
        'y_data': y_data,
        # 'z_data': z_data,
        'strikeSPX': strikeSPX,
        'wing1': wing1,
        'wing2': wing2,
        'wing3': wing3,
        'wing4': wing4,
        'credit': credit,
        'hayNoticias': hayNoticias
    }

    json_response = json.dumps(response_data)
    return json_response


@app.route('/update_table', methods=['POST'])
@login_required
def update_table():
    take_profit = float(request.form['takeProfit'])
    stop_loss = float(request.form['stopLoss'])
    contracts_ = float(request.form['contracts'])
    estrategia_Number_ = request.form['estrategiaNumber']
    offset_ = float(request.form['offset'])
    dollarOrPerc_ = request.form['dollarOrPerc']
    updated_data = funcionesV2.obtener_Datos(
        take_profit/100, stop_loss/100, contracts_, estrategia_Number_, offset_, dollarOrPerc_)

    return jsonify(updated_data)


@app.route('/comparative')
@login_required
def comparative():
    return render_template('/comparative.html')


@app.route('/comparative_data', methods=['POST'])
@login_required
def process_data():

    dollarOrPercen = request.form['dollarOrPercen']
    divisor = 100 if dollarOrPercen == 'P' else 1

    takeProfit1 = float(request.form['takeProfit1'])/divisor
    takeProfit2 = float(request.form['takeProfit2'])/divisor
    takeProfit3 = float(request.form['takeProfit3'])/divisor
    takeProfit4 = float(request.form['takeProfit4'])/divisor
    stopLoss1 = float(request.form['stopLoss1'])/divisor
    stopLoss2 = float(request.form['stopLoss2'])/divisor
    stopLoss3 = float(request.form['stopLoss3'])/divisor
    stopLoss4 = float(request.form['stopLoss4'])/divisor
    offset = float(request.form['offset'])
    contracts = float(request.form['contracts'])
    miniComparative = request.form.get('miniComparative')
    IBList = request.form.get('IBList').split(',')

    listaFechas, listaNews, lista1, lista2, lista3, lista4, credito1, credito2, credito3, credito4, TPMinutes1, TPMinutes2, TPMinutes3, TPMinutes4, profitOrLoss1, profitOrLoss2, profitOrLoss3, profitOrLoss4 = funcionesV2.listaComparativa(
        takeProfit1, takeProfit2, takeProfit3, takeProfit4, stopLoss1, stopLoss2, stopLoss3, stopLoss4, offset, contracts, miniComparative, IBList, dollarOrPercen)

    listaNombreDias = [obtener_nombre_dia(fecha) for fecha in listaFechas]

    listas = [listaFechas, listaNombreDias, listaNews, lista1,
              lista2, lista3, lista4, credito1, credito2, credito3, credito4, TPMinutes1, TPMinutes2, TPMinutes3, TPMinutes4, profitOrLoss1, profitOrLoss2, profitOrLoss3, profitOrLoss4]

    # convierte las listas en una unica lista poniendo "-" donde no hay datos!!!
    lista_unica = list(zip_longest(*listas))

    return jsonify(data=lista_unica)


@app.route('/ICminiComparative_data', methods=['POST'])
@login_required
def ICminiComparative_data():

    dollarOrPercen = request.form['ICdollarOrPercen']
    divisor = 100 if dollarOrPercen == 'P' else 1

    takeProfit1 = float(request.form['ICtakeProfit1'])/divisor
    takeProfit2 = float(request.form['ICtakeProfit2'])/divisor
    stopLoss1 = float(request.form['ICstopLoss1'])/divisor
    stopLoss2 = float(request.form['ICstopLoss2'])/divisor
    offset = float(request.form['ICoffset'])
    contracts = float(request.form['ICcontracts'])

    listaFechas, listaNews, lista1, lista2, = funcionesV2.ICminiListaComparativa(
        takeProfit1, takeProfit2, stopLoss1, stopLoss2, offset, contracts, dollarOrPercen)

    listaNombreDias = [obtener_nombre_dia(fecha) for fecha in listaFechas]

    listas = [listaFechas, listaNombreDias, listaNews, lista1,
              lista2]

    # convierte las listas en una unica lista poniendo "-" donde no hay datos!!!
    lista_unica = list(zip_longest(*listas))

    return jsonify(data=lista_unica)


@app.route('/saveOnDB', methods=['POST'])
def guardar_valores():
    try:
        data = request.get_json()  # Obtener los datos enviados desde el cliente
        ModelUser.saveData(db, data, session['userID'])
        return "Valores guardados correctamente", 200

    except Exception as e:
        return str(e), 400  # Devuelve un error 400 en caso de problemas


@app.route('/update_BestPair', methods=['POST'])
@login_required
def update_BestPair():
    data = request.get_json()
    dollarOrPerc = data.get('dollarOrPerc')
    newsDays = data.get('newsDays')
    estrategia = data.get('estrategia')
    resultsIn = data.get('resultsIn')
    contracts = float(data.get('contracts'))
    offset = float(data.get('offset'))
    sinceDate = data.get('sinceDate')
    untilDate = data.get('untilDate')
    dayFilter = int(data.get('dayFilter'))  # Obtener el nuevo parámetro 'dayFilter'

    dataIB1 = funcionesV2.obtenerBestPair(
        estrategia, dollarOrPerc, newsDays, resultsIn, contracts, offset, sinceDate, untilDate, dayFilter)
    return jsonify(dataIB1)


@app.route('/obtener_fear_and_greed')
def obtener_fear_and_greed():
    fearGreed = fear_and_greed.get()
    fear_greed_info = {
        "value": fearGreed.value,
        "description": fearGreed.description,
        "last_update": fearGreed.last_update.isoformat(),
    }

    return jsonify(fear_greed_info)


@app.route('/obtener_datos_best_pair')
@login_required
def obtener_datos_best_pair():
    datos = funcionesV2.obtener_datos_best_pair()
    return (datos)


@app.route('/logout')
def logout():
    logout_user()
    return redirect("https://opcionsigma.com")

    # return redirect(url_for('index'))


def obtener_nombre_dia(fecha_str):
    fecha = datetime.strptime(fecha_str, '%m/%d/%Y')
    nombre_dia = fecha.strftime('%A')  # %A devuelve el nombre completo del día
    return nombre_dia


@app.route('/saveOnDBVIPUser', methods=['POST'])
def saveONDBVIPuser():
    try:
        data = request.get_json()  # Obtener los datos enviados desde el cliente

        ModelUser.saveDataVIP(db, data['usernameVIP'], data['transactionID'])
        return "Saving OK", 200

    except Exception as e:
        return str(e), 400  # Devuelve un error 400 en caso de problemas


@app.route('/get_alerts_data')
@login_required
def get_alerts_data():
    data = funcionesV2.get_alert_data()
    return data


@app.route('/get_heatMapResultados')
@login_required
def get_heatMapResultados():
    data = funcionesV2.get_heatMapResultados()
    return data


@app.route('/get_boletinResultados')
@login_required
def get_boletinResultados():
    data = funcionesV2.get_boletinResultados()
    return data


@app.errorhandler(401)
def unauthorized(error):
    return render_template('error_401.html'), 401


@app.errorhandler(404)
def page_not_found(error):
    return render_template('error_404.html'), 404


@app.route('/submitOSLink', methods=['POST'])
def submitOSLink():
    # Recibir la URL desde el formulario
    url = request.form.get('user_input')

    # Configuración de Selenium
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--disable-dev-shm-usage")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument("--remote-debugging-port=9222")

    results = None  # Inicializamos la variable results

    service = Service('/usr/local/bin/chromedriver')
    driver = webdriver.Chrome(service=service, options=chrome_options)

    try:
        # Navegar a la URL proporcionada
        driver.get(url)
        # Extraer Net Credit
        net_credit_element = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.CLASS_NAME, "StrategyStatsPanel_icon__cost__1lKQm"))
        )
        parent_element = net_credit_element.find_element(By.XPATH, "..")
        net_credit_text = parent_element.text

        # Extraer Strikes y Fecha de Expiración
        header_element = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.XPATH, "//div[@role='alert']//h4"))
        )
        header_text = header_element.text.strip()

  # Asegurarnos de acceder al contenedor correcto y extraer "since"
        try:
            parent_div = header_element.find_element(By.XPATH, "..")  # Subimos al contenedor padre
            alert_paragraphs = parent_div.find_elements(By.XPATH, "./p")  # Buscamos todos los <p> en el contenedor padre
            since_date = "Fecha no encontrada"
            for paragraph in alert_paragraphs:
                alert_text = paragraph.text.strip()
                match = re.search(r'since (\w+ \d+, \d{4}, \d{1,2}:\d{2} [AP]M)', alert_text)
                if match:
                    since_date = match.group(1)
                    break

            if since_date != "Fecha no encontrada":
                try:
                    parsed_since_date = datetime.strptime(since_date, '%b %d, %Y, %I:%M %p')
                    since_date = parsed_since_date.strftime('%m/%d/%Y')  # Transformar al formato MM/DD/YYYY
                except ValueError:
                    since_date = "Fecha inválida"
        except Exception as e:
            print(f"Error al extraer texto: {str(e)}")
            since_date = "Fecha no encontrada"

        # Extraer Breakeven usando el mismo patrón
        breakeven_element = WebDriverWait(driver, 15).until(
            EC.presence_of_element_located((By.CLASS_NAME, "StrategyStatsPanel_icon__breakeven__w7hCB"))
        )
        breakeven_parent = breakeven_element.find_element(By.XPATH, "..")
        breakeven_text = breakeven_parent.text.strip()
        breakeven_status = breakeven_text.split("\n")[0].replace('$', '').replace(',', '')  # Remover comas

        # Extraer Max Loss
        try:
            max_loss_element = WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.XPATH, "//span[contains(text(),'Max loss')]/following-sibling::span"))
            )
            max_loss = max_loss_element.text.strip().replace('$', '').replace(',', '')  # Remover $ y ,
        except Exception as e:
            max_loss = f"Error al extraer Max Loss: {str(e)}"

        # Extraer Max Profit
        try:
            max_profit_element = WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.XPATH, "//span[contains(text(),'Max profit')]/following-sibling::span"))
            )
            max_profit = max_profit_element.text.strip().replace('$', '').replace(',', '')  # Remover $ y ,
        except Exception as e:
            max_profit = f"Error al extraer Max Profit: {str(e)}"

        # Abrir el modal de posiciones
        modal_button = WebDriverWait(driver, 15).until(
            EC.element_to_be_clickable((By.XPATH, "//button[contains(@class, 'button--small') and contains(text(), 'Positions')]"))
        )
        modal_button.click()

        # Intentar cargar posiciones hasta 3 veces
        for attempt in range(3):
            positions_elements = driver.find_elements(By.CLASS_NAME, "PositionList_item__vDwiz")
            all_positions_valid = True  # Bandera para verificar si todas las posiciones tienen detalles válidos

            positions = []
            for index, position in enumerate(positions_elements, start=1):
                try:
                    details_element = position.find_element(By.CLASS_NAME, "PositionList_item__open__ri52K")
                    details = details_element.text.strip() if details_element.text.strip() else "N/A"

                    price = "N/A"
                    if "at" in details:
                        price = details.split("at")[-1].strip()

                    if details == "N/A":  # Si no se encuentran detalles, marcamos la bandera como inválida
                        all_positions_valid = False

                    positions.append(f"Posición {index}: Detalles: {details}, Precio: {price}")
                except Exception as e:
                    all_positions_valid = False
                    positions.append(f"Posición {index}: Error al procesar detalles. {e}")

            if all_positions_valid and positions:  # Si todas las posiciones son válidas, salimos del bucle
                break

            time.sleep(2)  # Esperar entre intentos

        if not all_positions_valid:  # Si no se encontraron todas las posiciones después de 3 intentos
            return jsonify({"error": "No se encontraron todas las posiciones después de 3 intentos."})

        # Obtener expiration_date de las posiciones
        expiration_date = "Fecha no encontrada"
        if positions:  # Verificar que exista al menos una posición
            for position in positions:  # Iterar sobre las posiciones
                expiration_match = re.search(r'\d{1,2}/\d{1,2}/\d{2,4}', position)
                if expiration_match:
                    raw_date = expiration_match.group(0)
                    # Transformar al formato MM/DD/YYYY
                    try:
                        # Parsear la fecha considerando si el año es de 2 o 4 dígitos
                        if len(raw_date.split('/')[-1]) == 2:  # Si el año tiene 2 dígitos
                            parsed_date = datetime.strptime(raw_date, '%m/%d/%y')
                        else:  # Si el año tiene 4 dígitos
                            parsed_date = datetime.strptime(raw_date, '%m/%d/%Y')

                        expiration_date = parsed_date.strftime('%m/%d/%Y')
                        break  # Detener la búsqueda al encontrar una fecha válida
                    except ValueError:
                        continue  # Continuar con la siguiente posición si hay un error de formato

        result_text = f"""- Descripcion: {header_text}
- Fecha Apertura: {since_date}
- Fecha Expiracion: {expiration_date}
- Breakeven: {breakeven_status}
- Max Perdida: {max_loss}
- Max Ganancia: {max_profit}
- URL: {url}
"""

        # Retornar como texto plano
        return result_text, 200, {'Content-Type': 'text/plain'}

    except Exception as e:
        # Retornar un mensaje de error en texto plano
        return "ERROR en la captura de datos. Verifique el link o intente más tarde.", 500, {'Content-Type': 'text/plain'}

    finally:
        driver.quit()


PATH_UBUNTU = "/var/www/html/flask_project/"
pd.set_option('display.max_rows', None)  # Muestra todas las filas
# pd.set_option('display.max_columns', None)  # Muestra todas las columnas


# Modo Debug: True para ver mensajes en consola, False para ocultarlos
DEBUG_MODE = False


def debug_print(*args, **kwargs):
    """Imprime mensajes solo si DEBUG_MODE está activado."""
    if DEBUG_MODE:
        print(*args, **kwargs)


def load_csv(file_info):
    """Carga un CSV y devuelve su contenido."""
    file_date, file_path = file_info
    try:
        return file_date, pd.read_csv(file_path)
    except Exception as e:
        print(f"⚠ Error cargando {file_path}: {e}")
        return file_date, None  # Devolver None si hay error


def calculate_iron_condor_credit(df, sell_call, buy_call, sell_put, buy_put):
    """Calcula el crédito inicial del Iron Condor, validando que cada strike esté presente."""

    # Convertir a float para evitar problemas de comparación
    sell_call, buy_call, sell_put, buy_put = map(float, [sell_call, buy_call, sell_put, buy_put])

    # Asegurar que la columna 'strike' es float
    df['strike'] = df['strike'].astype(float)

    # Verificar si cada strike está presente en el DataFrame
    missing_strikes = [strike for strike in [sell_call, buy_call, sell_put, buy_put] if strike not in df['strike'].values]

    if missing_strikes:
        debug_print(f"⚠ Strikes faltantes en el DataFrame: {missing_strikes}")
        debug_print(f"📌 Strikes disponibles en el df: {df['strike'].unique()}")
        debug_print("❌ Día descartado debido a datos insuficientes para el Iron Condor.")
        return None  # ❌ Se retorna None para indicar que no se puede procesar

    try:
        # Obtener las filas correspondientes a cada strike
        sell_call_row = df[df['strike'] == sell_call].iloc[0]
        buy_call_row = df[df['strike'] == buy_call].iloc[0]
        sell_put_row = df[df['strike'] == sell_put].iloc[0]
        buy_put_row = df[df['strike'] == buy_put].iloc[0]

        # Calcular el precio promedio (Bid + Ask) / 2 para cada opción
        sell_call_price = (sell_call_row['bid_call'] + sell_call_row['ask_call']) / 2
        buy_call_price = (buy_call_row['bid_call'] + buy_call_row['ask_call']) / 2
        sell_put_price = (sell_put_row['bid_put'] + sell_put_row['ask_put']) / 2
        buy_put_price = (buy_put_row['bid_put'] + buy_put_row['ask_put']) / 2

        # Calcular el crédito inicial del Iron Condor
        credit = (sell_call_price - buy_call_price) + (sell_put_price - buy_put_price)

        debug_print("\n🔹 Valores de precios:")
        debug_print(f"📌 Sell Call ({sell_call}): {sell_call_price:.2f}")
        debug_print(f"📌 Buy Call ({buy_call}): {buy_call_price:.2f}")
        debug_print(f"📌 Sell Put ({sell_put}): {sell_put_price:.2f}")
        debug_print(f"📌 Buy Put ({buy_put}): {buy_put_price:.2f}")

        return credit

    except IndexError as e:
        debug_print(f"⚠ Error al seleccionar filas del DataFrame: {e}")
        return None

@app.route('/log_intraday', methods=['POST'])
def log_intraday():
    data = request.get_json()
    date_str = data.get('date')
    opening_time = data.get('openingTime')
    strikes = data.get('strikes', {})
    timeOcurr = data.get('occurTime')
    initialCredit = data.get('initialCredit')

    print("=== Datos recibidos para evolución intradía ===")
    print("Fecha:", date_str)
    print("Hora de apertura:", opening_time)
    print("Strikes:", strikes)
    print("HORA TP/SL:", timeOcurr)
    print("Credito:", initialCredit)

    # Aseguramos que la hora tenga el formato HH:MM:SS
    if len(opening_time.split(':')) == 2:
        opening_time += ":00"

    symbol = "SPX"  # Se asume, ajustar si es variable
    csv_filename = f"optionChain_${symbol}_{date_str}.csv"
    csv_path = os.path.join('/var/www/html/flask_project/chains', csv_filename)

    try:
        # Calcular el crédito inicial y obtener el timestamp elegido
        initial_credit, chosen_timestamp = get_initial_credit(
            csv_path,
            opening_time,
            strikes.get('sellCall'),
            strikes.get('buyCall'),
            strikes.get('sellPut'),
            strikes.get('buyPut')
        )

        if initial_credit is None or chosen_timestamp is None:
            return jsonify({'status': 'error', 'message': 'No se pudo calcular el crédito inicial'}), 400

        initial_credit = float(initialCredit)*100

        # Obtener la evolución del crédito a partir de chosen_timestamp
        evolution = get_credit_evolution(
            csv_path,
            chosen_timestamp,
            strikes.get('sellCall'),
            strikes.get('buyCall'),
            strikes.get('sellPut'),
            strikes.get('buyPut'),
            initial_credit
        )

        # Convertir los timestamps de la evolución a string para JSON
        evolution_serializable = [
            {
                'timestamp': rec['timestamp'].strftime('%Y-%m-%d %H:%M:%S'),
                'credit': rec['credit'],
                'profit_loss': rec['profit_loss']
            }
            for rec in evolution
        ]

        return jsonify({
            'status': 'success',
            'credit': initial_credit,
            'chosen_timestamp': chosen_timestamp.strftime('%Y-%m-%d %H:%M:%S'),
            'evolution': evolution_serializable,
            'message': 'Crédito inicial y evolución calculados correctamente'
        }), 200

    except Exception as e:
        print("Error al procesar el archivo:", e)
        return jsonify({'status': 'error', 'message': str(e)}), 500


def get_initial_credit(csv_file, target_horario, sell_call, buy_call, sell_put, buy_put):
    """
    csv_file: ruta del CSV (por ejemplo, "/var/www/html/flask_project/chains/optionChain_$SPX_2025-03-06.csv")
    target_horario: hora de apertura en formato "HH:MM:SS" (string)
    sell_call, buy_call, sell_put, buy_put: strikes (números o strings convertibles a float)

    Retorna:
      credit: crédito inicial calculado (multiplicado por 100)
      chosen_timestamp: el timestamp (datetime) del registro seleccionado
    """
    # Cargar el CSV
    df = pd.read_csv(csv_file)

    # Convertir la columna 'timestamp' a datetime
    df['timestamp'] = pd.to_datetime(df['timestamp'])

    # Extraer la parte de la hora
    df['only_time'] = df['timestamp'].dt.time

    # Convertir target_horario a objeto time
    target_time = datetime.strptime(target_horario, '%H:%M:%S').time()

    # Filtrar filas cuya hora sea >= target_time
    df_after = df[df['only_time'] >= target_time]

    if not df_after.empty:
        # De las filas con hora >= target, elegir la que tenga la mínima diferencia
        df_after['time_diff'] = df_after['only_time'].apply(
            lambda t: (datetime.combine(datetime.min, t) - datetime.combine(datetime.min, target_time)).total_seconds()
        )
        min_diff = df_after['time_diff'].min()
        chosen_rows = df_after[df_after['time_diff'] == min_diff]
    else:
        # Si no hay filas con hora >= target, se elige la fila más cercana en sentido absoluto
        df['time_diff'] = df['only_time'].apply(
            lambda t: abs((datetime.combine(datetime.min, t) - datetime.combine(datetime.min, target_time)).total_seconds())
        )
        min_diff = df['time_diff'].min()
        chosen_rows = df[df['time_diff'] == min_diff]

    # Tomamos la primera fila del resultado
    chosen_timestamp = chosen_rows.iloc[0]['timestamp']

    # Función auxiliar para obtener el mid-price
    def get_mid_price(df_rows, strike, bid_col, ask_col):
        row = df_rows[df_rows['strike'] == float(strike)]
        if row.empty:
            return None
        bid = float(row.iloc[0][bid_col])
        ask = float(row.iloc[0][ask_col])
        return (bid + ask) / 2

    mid_sell_call = get_mid_price(chosen_rows, sell_call, 'bid_call', 'ask_call')
    mid_buy_call = get_mid_price(chosen_rows, buy_call, 'bid_call', 'ask_call')
    mid_sell_put = get_mid_price(chosen_rows, sell_put, 'bid_put', 'ask_put')
    mid_buy_put = get_mid_price(chosen_rows, buy_put, 'bid_put', 'ask_put')

    if None in [mid_sell_call, mid_buy_call, mid_sell_put, mid_buy_put]:
        print("Error: No se encontraron datos para alguno de los strikes.")
        return None, None

    # Calcular el crédito inicial
    credit = ((mid_sell_call - mid_buy_call) + (mid_sell_put - mid_buy_put)) * 100
    return credit, chosen_timestamp


def compute_credit_for_group(group, sell_call, buy_call, sell_put, buy_put):
    """
    Calcula el crédito para un grupo de registros (mismo timestamp)
    utilizando las columnas bid/ask correspondientes a cada strike.
    Devuelve None si alguno de los strikes no se encuentra.
    """
    def get_mid(series_bid, series_ask):
        return (float(series_bid.iloc[0]) + float(series_ask.iloc[0])) / 2

    try:
        mid_sell_call = get_mid(group[group['strike'] == float(sell_call)]['bid_call'],
                                group[group['strike'] == float(sell_call)]['ask_call'])
        mid_buy_call = get_mid(group[group['strike'] == float(buy_call)]['bid_call'],
                               group[group['strike'] == float(buy_call)]['ask_call'])
        mid_sell_put = get_mid(group[group['strike'] == float(sell_put)]['bid_put'],
                               group[group['strike'] == float(sell_put)]['ask_put'])
        mid_buy_put = get_mid(group[group['strike'] == float(buy_put)]['bid_put'],
                              group[group['strike'] == float(buy_put)]['ask_put'])
    except IndexError:
        return None

    credit = ((mid_sell_call - mid_buy_call) + (mid_sell_put - mid_buy_put)) * 100
    return credit

def filter_outliers_by_neighbors(evolution, threshold=0.001, neighbors=5):
    """
    Recibe la lista 'evolution' y elimina registros cuyo crédito difiera más del 'threshold'
    respecto a la mediana de 'neighbors' vecinos a cada lado (más robusto que la media).
    
    Devuelve la lista filtrada.
    """
    df = pd.DataFrame(evolution)
    n = len(df)
    local_medians = []

    for i in range(n):
        start = max(0, i - neighbors)
        end = min(n, i + neighbors + 1)
        neighbor_indices = list(range(start, i)) + list(range(i + 1, end))
        if neighbor_indices:
            median_val = df.iloc[neighbor_indices]['credit'].median()
        else:
            median_val = np.nan
        local_medians.append(median_val)

    df['local_median'] = local_medians
    df['desvio_pct'] = abs(df['credit'] - df['local_median']) / df['local_median']
    df['outlier'] = df['desvio_pct'].apply(lambda x: pd.notnull(x) and x > threshold)

    # Mostrar por consola los registros marcados como outliers
    outliers = df[df['outlier']]
    if not outliers.empty:
        print("Timestamps marcados como outliers:")
        for _, row in outliers.iterrows():
            print(f"{row['timestamp']} - Credit: {row['credit']} - Local Median: {row['local_median']} - Desvio_pct: {row['desvio_pct']:.2f}")
    else:
        print("No se detectaron outliers con el método actualizado.")

    # Devolver evolución sin outliers
    df_filtrado = df[~df['outlier']].drop(columns=['local_median', 'desvio_pct', 'outlier'])
    return df_filtrado.to_dict('records')


def get_credit_evolution(csv_file, start_timestamp, sell_call, buy_call, sell_put, buy_put, initial_credit):
    """
    csv_file: ruta del CSV diario.
    start_timestamp: datetime a partir del cual se evaluará la evolución.
    sell_call, buy_call, sell_put, buy_put: strikes.
    initial_credit: el crédito inicial calculado.

    Devuelve una lista de diccionarios con:
      - timestamp (datetime)
      - credit (crédito calculado en ese timestamp)
      - profit_loss (diferencia entre initial_credit y el crédito actual)
      
    Antes de retornar, se filtran los registros (se eliminan) aquellos considerados outliers
    según que el crédito difiera en más del 5% respecto a la media de sus vecinos (anterior y siguiente).
    """
    # 1) Leer CSV y convertir la columna timestamp a datetime
    df = pd.read_csv(csv_file)
    df['timestamp'] = pd.to_datetime(df['timestamp'])
    
    # 2) Filtrar registros con timestamp >= start_timestamp
    df = df[df['timestamp'] >= start_timestamp]
    
    # 3) Construir la lista evolution agrupando por timestamp
    evolution = []
    for ts, group in df.groupby('timestamp'):
        credit = compute_credit_for_group(group, sell_call, buy_call, sell_put, buy_put)
        if credit is not None:
            profit_loss = initial_credit - credit
            evolution.append({
                'timestamp': ts,
                'credit': credit,
                'profit_loss': profit_loss
            })
    
    # 4) Ordenar evolution por timestamp
    evolution = sorted(evolution, key=lambda x: x['timestamp'])
    
    #  # 5) Generar un CSV con los datos de evolución sin filtrar (timestamp y credit)
    # df_evo = pd.DataFrame(evolution)
    # df_evo[['timestamp', 'credit']].to_csv("evolution_before_filtering.csv", index=False)
    # print("Archivo 'evolution_before_filtering.csv' generado.")
    
    # 5) Filtrar los outliers usando el método basado en vecinos
    evolution_filtered = filter_outliers_by_neighbors(evolution, threshold=0.05)
    
    # Retornar la evolución filtrada
    return evolution_filtered

def export_timestamps_and_credits(evolution, out_csv_file):
    """
    Guarda solo 'timestamp' y 'credit' de la lista 'evolution' en un CSV
    con dos columnas: timestamp, credit.
    """
    import csv

    # Abrir archivo CSV en modo escritura
    with open(out_csv_file, 'w', newline='') as f:
        writer = csv.writer(f)
        # Encabezado
        writer.writerow(["timestamp", "credit"])
        # Filas
        for row in evolution:
            writer.writerow([
                row['timestamp'].strftime('%Y-%m-%d %H:%M:%S'),
                row['credit']
            ])


# Endpoint para encolar la tarea de backtesting
@app.route('/process_form', methods=['POST'])
def process_form():
    data = request.get_json()
    # Aquí puedes agregar validaciones si es necesario.
    # Se encola la tarea de backtesting en segundo plano
    task = process_backtesting_task.delay(data)
    return jsonify({"task_id": task.id, "status": "Procesando..."})

# Endpoint para consultar el estado y resultado de la tarea


@app.route('/task_status/<task_id>', methods=['GET'])
def task_status(task_id):
    from tasks import aggregate_results
    result = aggregate_results.AsyncResult(task_id)
    if result.state == 'SUCCESS':
        return jsonify({"state": result.state, "result": result.result})
    else:
        return jsonify({"state": result.state, "result": None})


# MAIN *** MAIN *** MAIN
if __name__ == '__main__':
    app.run(debug=True, port=5002)
