from __future__ import annotations

import pandas as pd

from .constants import TREND_BEAR, TREND_BULL


def _trade_return_pct(direction: str, entry_price: float, exit_price: float) -> float:
    if direction == "LONG":
        return ((exit_price - entry_price) / entry_price) * 100.0
    return ((entry_price - exit_price) / entry_price) * 100.0


def backtest_signals(
    df: pd.DataFrame,
    slippage_bps: float = 0.0,
    fee_bps: float = 0.0,
    stop_loss_pct: float | None = None,
    take_profit_pct: float | None = None,
    max_hold_bars: int = 1,
    min_hold_bars: int = 1,
) -> dict[str, float | int]:
    if len(df) < 4:
        return {"trades": 0, "wins": 0, "losses": 0, "win_rate": 0.0, "avg_pnl_pct": 0.0}

    trend_series = df["trend_state"]
    trend_change = trend_series != trend_series.shift(1)
    signal_idx = df.index[trend_change & trend_series.isin([TREND_BULL, TREND_BEAR])]

    wins = 0
    losses = 0
    pnl_values: list[float] = []
    max_hold = max(1, int(max_hold_bars))
    min_hold = max(1, min(int(min_hold_bars), max_hold))
    total_cost_pct = ((float(slippage_bps) + float(fee_bps)) * 2.0) / 100.0

    for idx in signal_idx:
        pos = df.index.get_loc(idx)
        if pos + 1 >= len(df):
            continue

        entry_close = float(df.iloc[pos]["Close"])
        signal_trend = df.iloc[pos]["trend_state"]
        direction = "LONG" if signal_trend == TREND_BULL else "SHORT"

        exit_price = float(df.iloc[min(pos + max_hold, len(df) - 1)]["Close"])
        for hold in range(min_hold, max_hold + 1):
            exit_pos = min(pos + hold, len(df) - 1)
            candidate_exit = float(df.iloc[exit_pos]["Close"])
            raw_pnl = _trade_return_pct(direction, entry_close, candidate_exit)
            if stop_loss_pct is not None and raw_pnl <= -float(stop_loss_pct):
                exit_price = candidate_exit
                break
            if take_profit_pct is not None and raw_pnl >= float(take_profit_pct):
                exit_price = candidate_exit
                break
            exit_price = candidate_exit

        pnl_pct = _trade_return_pct(direction, entry_close, exit_price) - total_cost_pct
        pnl_values.append(pnl_pct)
        wins += int(pnl_pct > 0)
        losses += int(pnl_pct <= 0)

    trades = wins + losses
    win_rate = (wins / trades * 100.0) if trades else 0.0
    avg_pnl_pct = (sum(pnl_values) / len(pnl_values)) if pnl_values else 0.0
    return {
        "trades": trades,
        "wins": wins,
        "losses": losses,
        "win_rate": round(win_rate, 2),
        "avg_pnl_pct": round(avg_pnl_pct, 2),
    }


def simulate_trend_trades(df: pd.DataFrame, max_examples: int = 20) -> pd.DataFrame:
    if len(df) < 4:
        return pd.DataFrame(
            columns=[
                "direction",
                "entry_timestamp",
                "exit_timestamp",
                "entry_price",
                "exit_price",
                "bars_held",
                "pnl_pct",
                "outcome",
            ]
        )

    trend_series = df["trend_state"]
    trend_change = trend_series != trend_series.shift(1)
    signal_idx = df.index[trend_change & trend_series.isin([TREND_BULL, TREND_BEAR])]

    direction_by_trend = {
        TREND_BULL: "LONG",
        TREND_BEAR: "SHORT",
    }

    open_trade: dict[str, object] | None = None
    closed_trades: list[dict[str, object]] = []

    for idx in signal_idx:
        pos = int(df.index.get_loc(idx))
        trend_now = str(df.iloc[pos]["trend_state"])
        direction = direction_by_trend[trend_now]
        close_price = float(df.iloc[pos]["Close"])

        if open_trade is None:
            open_trade = {
                "direction": direction,
                "entry_timestamp": idx,
                "entry_pos": pos,
                "entry_price": close_price,
            }
            continue

        if open_trade["direction"] == direction:
            continue

        entry_price = float(open_trade["entry_price"])
        if str(open_trade["direction"]) == "LONG":
            pnl_pct = ((close_price - entry_price) / entry_price) * 100.0
        else:
            pnl_pct = ((entry_price - close_price) / entry_price) * 100.0

        closed_trades.append(
            {
                "direction": str(open_trade["direction"]),
                "entry_timestamp": open_trade["entry_timestamp"],
                "exit_timestamp": idx,
                "entry_price": round(entry_price, 4),
                "exit_price": round(close_price, 4),
                "bars_held": pos - int(open_trade["entry_pos"]),
                "pnl_pct": round(pnl_pct, 2),
                "outcome": "Win" if pnl_pct > 0 else ("Loss" if pnl_pct < 0 else "Flat"),
            }
        )

        # Flip into the new direction at the same confirmed reversal bar.
        open_trade = {
            "direction": direction,
            "entry_timestamp": idx,
            "entry_pos": pos,
            "entry_price": close_price,
        }

    if not closed_trades:
        return pd.DataFrame(
            columns=[
                "direction",
                "entry_timestamp",
                "exit_timestamp",
                "entry_price",
                "exit_price",
                "bars_held",
                "pnl_pct",
                "outcome",
            ]
        )

    trades_df = pd.DataFrame(closed_trades)
    if max_examples > 0 and len(trades_df) > max_examples:
        trades_df = trades_df.iloc[-max_examples:].copy()
    return trades_df
