import pandas as pd
import math
import os
from datetime import datetime

PATH_UBUNTU = "/var/www/html/flask_project/"

RED = "\033[91m"
GREEN = "\033[92m"
RESET = "\033[0m"


def colorize(value):
    if isinstance(value, (int, float)):
        color = GREEN if value >= 0 else RED
        return f"{color}{value}{RESET}"
    return value


# ========================= CONFIGURACION GENERAL =========================

DESPLAZAMIENTOS = {
    "cons": 0,
    "inte": -5,
    "agre": -10,
    "ultr": -15
}

desde = "2026-02-01"
hasta = "2026-02-19"
symbol = "XSP"
estrategia = "Vertical"
riesgo = "agre"

CREDITO_TARGET = 50   # dolares

ARCHIVO_TOP_HORARIOS = "top3_horarios_SPX_Vertical_ultr.csv"

# ========================= HORA FIJA O DINAMICA =========================
# Para usar una hora especifica, definir el string de 4 digitos. Ej: "1020"
# Para usar el horario dinamico del CSV, dejar en None
HORA_FIJA = "1020" # = None
# HORA_FIJA = "1020"
# ===========================================================================

factor = 5 if symbol in ["SPY", "QQQ", "XSP"] else 1
desplazamiento = DESPLAZAMIENTOS.get(riesgo, 0) / factor

modo_hora = HORA_FIJA if HORA_FIJA else "DINAMICA (Top 1 semana siguiente)"

print("\n" + "="*70)
print("BTM CREDIT TARGET BACKTEST (HORA DINAMICA)".center(70))
print("="*70)
print(f"{'Fecha Desde:':25} {desde}")
print(f"{'Fecha Hasta:':25} {hasta}")
print(f"{'Simbolo:':25} {symbol}")
print(f"{'Estrategia:':25} {estrategia}")
print(f"{'Hora Entrada:':25} {modo_hora}")
print(f"{'Riesgo:':25} {riesgo}")
print(f"{'Credito Target ($):':25} {CREDITO_TARGET}")
print("="*70 + "\n")


# ========================= CARGA TOP HORARIOS =========================

def cargar_ventanas_top_horarios(archivo_top, rank_objetivo=1):
    """
    Logica correcta:
      - El CSV tiene filas donde end_date es el viernes en que se calculo el ranking.
      - La hora Top 1 de esa fila aplica para la semana SIGUIENTE al end_date.
      - Es decir: apply_from = end_date + 1 dia
                  apply_until = end_date de la fila siguiente (el proximo viernes de calculo)

    Ejemplo:
      end_date=2026-02-13, hora=1020
      -> aplica desde 2026-02-14 hasta 2026-02-20 (el proximo end_date)
      -> por lo tanto lunes 16, martes 17, mierc 18, jueves 19, viernes 20 usan hora 1020
    """
    df_top = pd.read_csv(archivo_top)
    df_top = df_top[df_top['rank'] == rank_objetivo].copy()
    df_top['end_date'] = pd.to_datetime(df_top['end_date'])
    df_top['hora_str'] = df_top['hora'].apply(lambda h: str(int(h)).zfill(4))
    df_top = df_top.sort_values('end_date').reset_index(drop=True)

    ventanas = []
    for i, row in df_top.iterrows():
        apply_from = row['end_date'] + pd.Timedelta(days=1)

        if i + 1 < len(df_top):
            apply_until = df_top.loc[i + 1, 'end_date']
        else:
            apply_until = pd.Timestamp("2099-12-31")

        ventanas.append((apply_from, apply_until, row['hora_str']))

    return ventanas


def obtener_hora_para_fecha(fecha_dt, ventanas):
    for apply_from, apply_until, hora_str in ventanas:
        if apply_from <= fecha_dt <= apply_until:
            return hora_str
    return None


# ========================= FUNCIONES =========================

def buscar_entrada_por_credito(
    archivo_option_chain, strike1, strike2, option_type, entry_time, credito_target
):
    try:
        df = pd.read_csv(archivo_option_chain)
        df['timestamp'] = pd.to_datetime(df['timestamp'])
        df = df[df['timestamp'] >= entry_time]
    except:
        return None, None

    es_primera_hora = True

    for ts, snap in df.groupby('timestamp'):
        s1 = snap[snap['strike'] == strike1]
        s2 = snap[snap['strike'] == strike2]
        if s1.empty or s2.empty:
            continue

        if option_type == "CALL":
            p1 = (s1['bid_call'].iloc[0] + s1['ask_call'].iloc[0]) / 2
            p2 = (s2['bid_call'].iloc[0] + s2['ask_call'].iloc[0]) / 2
        else:
            p1 = (s1['bid_put'].iloc[0] + s1['ask_put'].iloc[0]) / 2
            p2 = (s2['bid_put'].iloc[0] + s2['ask_put'].iloc[0]) / 2

        credit_dollars = (p1 - p2) * 100

        if credit_dollars >= credito_target:
            if es_primera_hora:
                return p1 - p2, ts
            else:
                return credito_target / 100, ts

        es_primera_hora = False

    return None, None


def obtener_ultimo_underlying_price(archivo_option_chain):
    try:
        df = pd.read_csv(archivo_option_chain)
        df['timestamp'] = pd.to_datetime(df['timestamp'])
        return df.sort_values('timestamp')['underlying_price'].iloc[-1]
    except:
        return None


# ========================= BACKTEST PRINCIPAL =========================

def backtest_credito_target_dinamico(ventanas, desde, hasta, symbol, estrategia, desplazamiento):
    resultados = []

    # Si hay hora fija, solo cargar ese archivo de strikes
    # Si es dinamico, cargar todos los archivos de horas unicas en las ventanas
    if HORA_FIJA:
        horas_unicas = [HORA_FIJA]
    else:
        horas_unicas = list(set(v[2] for v in ventanas))

    path_makekos = "/var/www/html/backtestingmarket/predictor_data/makekos/"
    strikes_por_hora = {}
    for hora in horas_unicas:
        archivo_strikes = f"{path_makekos}{symbol}/{symbol}_{estrategia}_strikes_{hora}.csv"
        try:
            df_s = pd.read_csv(archivo_strikes)
            df_s['Day'] = pd.to_datetime(df_s['Day'])
            df_s['Strike1_Desplazado'] = df_s.apply(
                lambda r: r['Strike1'] + desplazamiento if r['Option_Type'] == 'CALL'
                else r['Strike1'] - desplazamiento, axis=1
            ).astype(int)
            df_s['Strike2_Desplazado'] = df_s.apply(
                lambda r: r['Strike2'] + desplazamiento if r['Option_Type'] == 'CALL'
                else r['Strike2'] - desplazamiento, axis=1
            ).astype(int)
            strikes_por_hora[hora] = df_s
            print(f"OK Strikes cargados para hora {hora}")
        except FileNotFoundError:
            print(f"WARN Archivo no encontrado para hora {hora}: {archivo_strikes}")
            strikes_por_hora[hora] = None

    desde_dt = pd.to_datetime(desde)
    hasta_dt = pd.to_datetime(hasta)

    todas_fechas = set()
    for df_s in strikes_por_hora.values():
        if df_s is not None:
            fechas = df_s[(df_s['Day'] >= desde_dt) & (df_s['Day'] <= hasta_dt)]['Day']
            todas_fechas.update(fechas.dt.date.tolist())

    print(f"\nTotal fechas de trading encontradas: {len(todas_fechas)}")
    print("Procesando...\n")

    for fecha_date in sorted(todas_fechas):
        fecha_dt = pd.Timestamp(fecha_date)
        fecha_str = fecha_date.strftime("%Y-%m-%d")

        # Hora fija o dinamica
        if HORA_FIJA:
            hora = HORA_FIJA
        else:
            hora = obtener_hora_para_fecha(fecha_dt, ventanas)
            if hora is None:
                print(f"WARN Sin hora Top 1 para {fecha_str} (no cae en ninguna ventana), saltando...")
                continue

        df_s = strikes_por_hora.get(hora)
        if df_s is None:
            continue

        fila = df_s[df_s['Day'].dt.date == fecha_date]
        if fila.empty:
            print(f"WARN Sin strike para {fecha_str} en hora {hora}, saltando...")
            continue

        r = fila.iloc[0]
        option_type = r['Option_Type']
        strike1 = r['Strike1_Desplazado']
        strike2 = r['Strike2_Desplazado']

        hora_fmt = f"{hora[:2]}:{hora[2:]}"
        entry_time = pd.to_datetime(f"{fecha_str} {hora_fmt}") + pd.Timedelta(seconds=30)

        if symbol in ["SPX", "RUT", "XSP"]:
            archivo = f"{PATH_UBUNTU}chains/optionChain_${symbol}_{fecha_str}.csv"
        else:
            archivo = f"{PATH_UBUNTU}chains/optionChain_{symbol}_{fecha_str}.csv"

        credit_entry, entry_ts = buscar_entrada_por_credito(
            archivo, strike1, strike2, option_type, entry_time, CREDITO_TARGET
        )

        close_price = obtener_ultimo_underlying_price(archivo)

        if credit_entry is None:
            resultados.append({
                "Day": fecha_str,
                "Time": hora,
                "Strike1": strike1,
                "Strike2": strike2,
                "Option": option_type,
                "EntryTime": None,
                "EntryCredit": None,
                "ClosePrice": close_price,
                "Exit": "NO_ENTRY",
                "P/L": 0.0
            })
            continue

        spread = abs(strike1 - strike2)

        if option_type == "CALL":
            strike_venta = min(strike1, strike2)
            strike_compra = max(strike1, strike2)
            if close_price > strike_compra:
                pnl = (credit_entry - spread) * 100
            elif close_price < strike_venta:
                pnl = credit_entry * 100
            else:
                pnl = credit_entry * 100 - (close_price - strike_venta) * 100
        else:
            strike_venta = max(strike1, strike2)
            strike_compra = min(strike1, strike2)
            if close_price < strike_compra:
                pnl = (credit_entry - spread) * 100
            elif close_price > strike_venta:
                pnl = credit_entry * 100
            else:
                pnl = credit_entry * 100 - (strike_venta - close_price) * 100

        resultados.append({
            "Day": fecha_str,
            "Time": hora,
            "Strike1": strike1,
            "Strike2": strike2,
            "Option": option_type,
            "EntryTime": entry_ts,
            "EntryCredit": round(credit_entry * 100, 2),
            "ClosePrice": close_price,
            "Exit": "CLOSE",
            "P/L": round(pnl, 2)
        })

    return pd.DataFrame(resultados)


# ========================= EJECUCION =========================

if HORA_FIJA:
    ventanas = []
    print(f"Modo HORA FIJA activado: {HORA_FIJA}")
    print(f"(Se ignora el archivo: {ARCHIVO_TOP_HORARIOS})\n")
else:
    ventanas = cargar_ventanas_top_horarios(ARCHIVO_TOP_HORARIOS, rank_objetivo=1)
    print("Ventanas de aplicacion (ultimas 5):")
    for v in ventanas[-5:]:
        print(f"  Desde {v[0].date()} hasta {v[1].date()} -> hora {v[2]}")
    print()

df_resultados = backtest_credito_target_dinamico(
    ventanas, desde, hasta, symbol, estrategia, desplazamiento
)

profit_total = df_resultados['P/L'].sum()
wins = (df_resultados['P/L'] > 0).sum()
losses = (df_resultados['P/L'] < 0).sum()
no_trades = (df_resultados['Exit'] == "NO_ENTRY").sum()
win_rate = (wins / (wins + losses) * 100) if (wins + losses) > 0 else 0

df_console = df_resultados.copy()
df_console['P/L'] = df_console['P/L'].apply(colorize)

print(df_console.to_string(index=False))

print("\n================ RESUMEN =================")
print(f"Profit Total: ${profit_total:.2f}")
print(f"Wins: {wins} | Losses: {losses}")
print(f"No Trade Days: {no_trades}")
print(f"Win Rate: {win_rate:.2f}%")
print(f"Credito Target: ${CREDITO_TARGET}")
print(f"Modo Hora: {modo_hora}")

# ========================= GUARDAR CSV =========================

os.makedirs("resultados", exist_ok=True)

separator = pd.DataFrame([[""] * len(df_resultados.columns)], columns=df_resultados.columns)

summary_data = [
    ("===== SUMMARY =====", ""),
    ("Riesgo", riesgo),
    ("Hora", modo_hora),
    ("Profit Total", round(profit_total, 2)),
    ("Wins", wins),
    ("Losses", losses),
    ("No Trade Days", no_trades),
    ("Win Rate (%)", round(win_rate, 2)),
    ("Credito Target", CREDITO_TARGET),
    ("Generado", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
]

summary_rows = []
for label, value in summary_data:
    row = {col: "" for col in df_resultados.columns}
    row[df_resultados.columns[0]] = label
    row[df_resultados.columns[-1]] = value
    summary_rows.append(row)

df_summary = pd.DataFrame(summary_rows)
df_final = pd.concat([df_resultados, separator, df_summary], ignore_index=True)

hora_label = HORA_FIJA if HORA_FIJA else "DINAMICA"
output_file = (
    f"resultados/P&L_{estrategia}_{symbol}_HORA_{hora_label}_"
    f"{desde.replace('-','')}_{hasta.replace('-','')}_{riesgo}_CT_{CREDITO_TARGET}.csv"
)
df_final.to_csv(output_file, index=False)
print(f"\nCSV guardado en: {output_file}\n")