import pandas as pd
from pathlib import Path
import sys

BASE_PATH = Path(__file__).resolve().parents[2]
SRC_PATH = BASE_PATH / "src"

if str(SRC_PATH) not in sys.path:
    sys.path.append(str(SRC_PATH))

from research.entry_refinement_threshold_test_v1 import (
    build_frozen_signals,
    find_entry_0786,
)
from ingestion.price_loader_15m import load_eurusd_15m


def get_first_m15_idx_at_or_after(m15: pd.DataFrame, ts: pd.Timestamp):
    idx = m15["datetime"].searchsorted(ts, side="left")
    if idx >= len(m15):
        return None
    return int(idx)


def simulate_trade_with_early_exit(
    m15: pd.DataFrame,
    entry_time: pd.Timestamp,
    entry_price: float,
    signal: int,
    hold_hours: int,
    tail_stop: float,
    early_exit_hours: int | None,
    spread_cost: float,
):
    entry_idx = get_first_m15_idx_at_or_after(m15, entry_time)
    if entry_idx is None:
        return None

    hold_bars = hold_hours * 4
    exit_idx = min(entry_idx + hold_bars, len(m15) - 1)

    path = m15.iloc[entry_idx : exit_idx + 1].copy()
    if path.empty:
        return None

    # 1) Check early exit first, if configured
    if early_exit_hours is not None:
        ee_bars = early_exit_hours * 4
        ee_idx = min(entry_idx + ee_bars, len(m15) - 1)
        ee_row = m15.iloc[ee_idx]
        ee_price = float(ee_row["close"])

        if signal == 1:
            early_pnl = ee_price / entry_price - 1
        else:
            early_pnl = -(ee_price / entry_price - 1)

        if early_pnl < 0:
            ret = early_pnl - spread_cost
            return {
                "entry_time": entry_time,
                "exit_time": ee_row["datetime"],
                "signal": signal,
                "entry_price": entry_price,
                "exit_price": ee_price,
                "return": ret,
                "exit_type": f"early_exit_{early_exit_hours}h",
            }

    # 2) Otherwise use tail stop / full hold
    exit_price = float(path.iloc[-1]["close"])

    if signal == 1:
        adverse = (path["low"].min() - entry_price) / entry_price
        stop_hit = adverse < -tail_stop
        ret = exit_price / entry_price - 1
        if stop_hit:
            ret = -tail_stop
            exit_type = "tail_stop"
        else:
            exit_type = "time_exit"
    else:
        adverse = (path["high"].max() - entry_price) / entry_price
        stop_hit = adverse > tail_stop
        ret = -(exit_price / entry_price - 1)
        if stop_hit:
            ret = -tail_stop
            exit_type = "tail_stop"
        else:
            exit_type = "time_exit"

    ret -= spread_cost

    return {
        "entry_time": entry_time,
        "exit_time": path.iloc[-1]["datetime"],
        "signal": signal,
        "entry_price": entry_price,
        "exit_price": exit_price,
        "return": ret,
        "exit_type": exit_type,
    }


def run_backtest(tail_stop, early_exit_hours):
    allowed_hours = set(range(7, 17))  # London + NY
    threshold = 2.75
    hold_hours = 48
    spread_cost = 0.0001
    wait_hours = 6

    signals = build_frozen_signals(
        threshold=threshold,
        allowed_hours=allowed_hours,
    )

    m15 = load_eurusd_15m().copy()
    m15 = m15.sort_values("datetime").reset_index(drop=True)

    trades = []
    last_exit_time = None

    for _, sig in signals.iterrows():
        signal_time = sig["datetime"]

        if last_exit_time is not None and signal_time < last_exit_time:
            continue

        entry = find_entry_0786(
            m15=m15,
            signal_row=sig,
            wait_hours=wait_hours,
        )
        if entry is None:
            continue

        trade = simulate_trade_with_early_exit(
            m15=m15,
            entry_time=entry["entry_time"],
            entry_price=entry["entry_price"],
            signal=int(sig["signal"]),
            hold_hours=hold_hours,
            tail_stop=tail_stop,
            early_exit_hours=early_exit_hours,
            spread_cost=spread_cost,
        )
        if trade is None:
            continue

        trades.append(trade)
        last_exit_time = trade["exit_time"]

    trades = pd.DataFrame(trades)

    if trades.empty:
        return {
            "tail_stop": tail_stop,
            "early_exit_hours": early_exit_hours,
            "trades": 0,
            "win_rate": None,
            "avg_return": None,
            "final_equity": None,
            "max_drawdown": None,
            "max_losing_streak": None,
            "worst_day": None,
        }

    trades["equity_curve"] = (1 + trades["return"]).cumprod()
    trades["running_peak"] = trades["equity_curve"].cummax()
    trades["drawdown"] = trades["equity_curve"] / trades["running_peak"] - 1

    # losing streak
    max_losing_streak = 0
    current_streak = 0
    for r in trades["return"]:
        if r <= 0:
            current_streak += 1
            max_losing_streak = max(max_losing_streak, current_streak)
        else:
            current_streak = 0

    trades["entry_date"] = pd.to_datetime(trades["entry_time"]).dt.date
    daily_returns = trades.groupby("entry_date")["return"].sum()

    return {
        "tail_stop": tail_stop,
        "early_exit_hours": early_exit_hours if early_exit_hours is not None else "none",
        "trades": len(trades),
        "win_rate": (trades["return"] > 0).mean(),
        "avg_return": trades["return"].mean(),
        "final_equity": trades["equity_curve"].iloc[-1],
        "max_drawdown": trades["drawdown"].min(),
        "max_losing_streak": max_losing_streak,
        "worst_day": daily_returns.min(),
    }


def run_test():
    configs = [
        # baseline reference
        {"tail_stop": 0.0025, "early_exit_hours": None},
        # tail stop framework
        {"tail_stop": 0.0075, "early_exit_hours": 12},
        {"tail_stop": 0.0075, "early_exit_hours": 24},
        {"tail_stop": 0.0075, "early_exit_hours": 36},
        {"tail_stop": 0.0100, "early_exit_hours": 12},
        {"tail_stop": 0.0100, "early_exit_hours": 24},
        {"tail_stop": 0.0100, "early_exit_hours": 36},
    ]

    results = []
    for cfg in configs:
        results.append(run_backtest(**cfg))

    result_df = pd.DataFrame(results)

    print("\n=== TAIL STOP + EARLY EXIT LADDER V1 ===\n")
    print(result_df.to_string(index=False))

    print("\nBest by final equity:")
    print(result_df.sort_values("final_equity", ascending=False).head(1).to_string(index=False))

    print("\nBest by lowest drawdown:")
    print(result_df.sort_values("max_drawdown", ascending=False).head(1).to_string(index=False))


if __name__ == "__main__":
    run_test()