from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal
from typing import Any

from .client import as_decimal, decimal_text


@dataclass(frozen=True)
class Candle:
    open_time: int
    open: Decimal
    high: Decimal
    low: Decimal
    close: Decimal
    volume: Decimal


def parse_klines(rows: list[list[Any]]) -> list[Candle]:
    candles: list[Candle] = []
    for row in rows:
        if not isinstance(row, list) or len(row) < 6:
            continue
        candle = Candle(
            open_time=int(row[0]),
            open=as_decimal(row[1]),
            high=as_decimal(row[2]),
            low=as_decimal(row[3]),
            close=as_decimal(row[4]),
            volume=as_decimal(row[5]),
        )
        if candle.close > 0 and candle.high > 0 and candle.low > 0:
            candles.append(candle)
    # The newest REST kline can still be forming. Stable decisions use only closed candles.
    return candles[:-1] if len(candles) > 1 else candles


def ema(values: list[Decimal], period: int) -> list[Decimal]:
    if not values or period <= 0:
        return []
    multiplier = Decimal("2") / Decimal(period + 1)
    result = [values[0]]
    for value in values[1:]:
        result.append((value - result[-1]) * multiplier + result[-1])
    return result


def rsi(values: list[Decimal], period: int = 14) -> Decimal | None:
    if len(values) <= period:
        return None
    changes = [values[index] - values[index - 1] for index in range(1, len(values))]
    gains = [max(change, Decimal("0")) for change in changes]
    losses = [max(-change, Decimal("0")) for change in changes]
    average_gain = sum(gains[:period], Decimal("0")) / Decimal(period)
    average_loss = sum(losses[:period], Decimal("0")) / Decimal(period)
    for index in range(period, len(changes)):
        average_gain = ((average_gain * Decimal(period - 1)) + gains[index]) / Decimal(period)
        average_loss = ((average_loss * Decimal(period - 1)) + losses[index]) / Decimal(period)
    if average_loss == 0:
        return Decimal("100")
    relative_strength = average_gain / average_loss
    return Decimal("100") - (Decimal("100") / (Decimal("1") + relative_strength))


def atr(candles: list[Candle], period: int = 14) -> Decimal | None:
    if len(candles) <= period:
        return None
    ranges: list[Decimal] = []
    for index in range(1, len(candles)):
        candle = candles[index]
        previous_close = candles[index - 1].close
        ranges.append(
            max(
                candle.high - candle.low,
                abs(candle.high - previous_close),
                abs(candle.low - previous_close),
            )
        )
    average = sum(ranges[:period], Decimal("0")) / Decimal(period)
    for value in ranges[period:]:
        average = ((average * Decimal(period - 1)) + value) / Decimal(period)
    return average


def macd(values: list[Decimal]) -> tuple[Decimal, Decimal, Decimal] | None:
    if len(values) < 35:
        return None
    fast = ema(values, 12)
    slow = ema(values, 26)
    macd_line = [fast[index] - slow[index] for index in range(len(values))]
    signal_line = ema(macd_line, 9)
    current_macd = macd_line[-1]
    current_signal = signal_line[-1]
    return current_macd, current_signal, current_macd - current_signal


def timeframe_analysis(interval: str, rows: list[list[Any]]) -> dict[str, Any]:
    candles = parse_klines(rows)
    if len(candles) < 210:
        raise ValueError(f"Not enough closed candles for {interval} analysis.")

    closes = [candle.close for candle in candles]
    volumes = [candle.volume for candle in candles]
    ema20 = ema(closes, 20)[-1]
    ema50 = ema(closes, 50)[-1]
    ema200 = ema(closes, 200)[-1]
    current_rsi = rsi(closes)
    current_atr = atr(candles)
    current_macd = macd(closes)
    if current_rsi is None or current_atr is None or current_macd is None:
        raise ValueError(f"Indicators could not be calculated for {interval}.")

    close = closes[-1]
    macd_line, signal_line, histogram = current_macd
    average_volume = sum(volumes[-20:], Decimal("0")) / Decimal("20")
    volume_ratio = volumes[-1] / average_volume if average_volume > 0 else Decimal("0")
    prior_window = candles[-21:-1]
    recent_support = min((candle.low for candle in prior_window), default=close)
    recent_resistance = max((candle.high for candle in prior_window), default=close)
    support_candidates = [
        value for value in (recent_support, ema20, ema50)
        if Decimal("0") < value < close
    ]
    structural_support = max(support_candidates, default=recent_support)
    latest_candle = candles[-1]
    previous_close = candles[-2].close
    fake_breakout = (
        latest_candle.high > recent_resistance
        and close <= recent_resistance
        and latest_candle.high - close >= current_atr * Decimal("0.25")
    )
    breakout_confirmed = (
        previous_close <= recent_resistance
        and close > recent_resistance
        and volume_ratio >= Decimal("1.20")
    )
    atr_percent = (current_atr / close) * Decimal("100") if close > 0 else Decimal("0")
    extension_from_ema20_atr = (
        (close - ema20) / current_atr if current_atr > 0 else Decimal("0")
    )

    score = Decimal("0")
    reasons: list[str] = []
    if close > ema20:
        score += Decimal("1")
        reasons.append("price above EMA20")
    else:
        score -= Decimal("1")
        reasons.append("price below EMA20")
    if ema20 > ema50:
        score += Decimal("1")
        reasons.append("EMA20 above EMA50")
    else:
        score -= Decimal("1")
        reasons.append("EMA20 below EMA50")
    if close > ema200:
        score += Decimal("1")
        reasons.append("price above EMA200")
    else:
        score -= Decimal("1")
        reasons.append("price below EMA200")
    if histogram > 0:
        score += Decimal("1")
        reasons.append("MACD momentum positive")
    else:
        score -= Decimal("1")
        reasons.append("MACD momentum negative")

    if current_rsi >= Decimal("55"):
        score += Decimal("1")
        reasons.append("RSI supports buyers")
    elif current_rsi <= Decimal("45"):
        score -= Decimal("1")
        reasons.append("RSI supports sellers")
    else:
        reasons.append("RSI neutral")

    if current_rsi >= Decimal("72"):
        score -= Decimal("0.75")
        reasons.append("RSI overbought risk")
    elif current_rsi <= Decimal("28"):
        score += Decimal("0.5")
        reasons.append("RSI oversold rebound potential")

    if volume_ratio >= Decimal("1.2"):
        score += Decimal("0.5") if score > 0 else Decimal("-0.5")
        reasons.append("volume confirms momentum")

    normalized_score = max(Decimal("-1"), min(Decimal("1"), score / Decimal("5.5")))
    trend = "BULLISH" if normalized_score >= Decimal("0.25") else (
        "BEARISH" if normalized_score <= Decimal("-0.25") else "MIXED"
    )
    return {
        "interval": interval,
        "trend": trend,
        "score": format(normalized_score, ".3f"),
        "close": decimal_text(close),
        "ema20": decimal_text(ema20),
        "ema50": decimal_text(ema50),
        "ema200": decimal_text(ema200),
        "rsi14": format(current_rsi, ".2f"),
        "atr14": decimal_text(current_atr),
        "macd": decimal_text(macd_line),
        "macd_signal": decimal_text(signal_line),
        "macd_histogram": decimal_text(histogram),
        "volume_ratio": format(volume_ratio, ".2f"),
        "support": decimal_text(structural_support),
        "resistance": decimal_text(recent_resistance),
        "fake_breakout": fake_breakout,
        "breakout_confirmed": breakout_confirmed,
        "atr_percent": format(atr_percent, ".2f"),
        "extension_from_ema20_atr": format(extension_from_ema20_atr, ".2f"),
        "reasons": reasons,
    }


def trade_plan(
    symbol: str,
    analyses: list[dict[str, Any]],
    *,
    account_value_usdt: Decimal,
    free_usdt: Decimal,
    risk_percent: Decimal,
    max_allocation_percent: Decimal,
    position_held: bool = True,
    current_price: Decimal | None = None,
    market_context: dict[str, Any] | None = None,
    min_combined_score: Decimal = Decimal("0.65"),
    min_confidence_percent: Decimal = Decimal("79"),
    min_volume_ratio: Decimal = Decimal("1.20"),
    max_daily_move_percent: Decimal = Decimal("12"),
    max_stop_risk_percent: Decimal = Decimal("7"),
    min_reward_risk: Decimal = Decimal("2"),
) -> dict[str, Any]:
    required_intervals = ("1h", "4h", "1d")
    by_interval = {str(item.get("interval")): item for item in analyses}
    required_analyses = [by_interval[item] for item in required_intervals if item in by_interval]
    weights = {"1h": Decimal("1"), "4h": Decimal("2"), "1d": Decimal("3")}
    weighted_score = Decimal("0")
    total_weight = Decimal("0")
    for item in required_analyses:
        weight = weights.get(str(item.get("interval")), Decimal("1"))
        weighted_score += as_decimal(item.get("score")) * weight
        total_weight += weight
    combined = weighted_score / total_weight if total_weight else Decimal("0")
    confidence = min(Decimal("95"), Decimal("50") + (abs(combined) * Decimal("45")))

    primary = by_interval.get("4h") or (analyses[0] if analyses else {})
    closed_price = as_decimal(primary.get("close"))
    price = current_price if current_price is not None and current_price > 0 else closed_price
    volatility = as_decimal(primary.get("atr14"))
    if volatility <= 0:
        volatility = price * Decimal("0.02")

    support = as_decimal(primary.get("support"))
    resistance = as_decimal(primary.get("resistance"))
    if support <= 0 or support >= closed_price:
        support = max(Decimal("0"), closed_price - volatility)

    entry_low = max(
        Decimal("0"),
        closed_price - (volatility * Decimal("0.25")),
        support + (volatility * Decimal("0.05")),
    )
    entry_high = closed_price + (volatility * Decimal("0.10"))
    stop_loss = max(
        Decimal("0"),
        min(
            entry_low - (volatility * Decimal("0.75")),
            support - (volatility * Decimal("0.20")),
        ),
    )
    unit_risk = price - stop_loss
    target_1 = price + (unit_risk * min_reward_risk) if unit_risk > 0 else Decimal("0")
    target_2 = price + (unit_risk * Decimal("3")) if unit_risk > 0 else Decimal("0")
    stop_risk_percent = (unit_risk / price) * Decimal("100") if price > 0 else Decimal("999")
    actual_reward_risk = (
        (target_1 - price) / unit_risk if unit_risk > 0 else Decimal("0")
    )

    all_required_present = len(required_analyses) == len(required_intervals)
    higher_timeframes_bullish = all_required_present and all(
        by_interval.get(interval, {}).get("trend") == "BULLISH"
        for interval in ("4h", "1d")
    )
    one_hour_trend = by_interval.get("1h", {}).get("trend")
    one_hour_score = as_decimal(by_interval.get("1h", {}).get("score"))
    one_hour_entry_ready = all_required_present and (
        one_hour_trend == "BULLISH"
        or (one_hour_trend == "MIXED" and one_hour_score >= Decimal("0"))
    )
    no_required_bearish = all_required_present and all(
        item.get("trend") != "BEARISH" for item in required_analyses
    )
    one_hour = by_interval.get("1h", {})
    four_hour = by_interval.get("4h", {})
    one_hour_volume = as_decimal(one_hour.get("volume_ratio"))
    four_hour_volume = as_decimal(four_hour.get("volume_ratio"))
    volume_confirmed = (
        max(one_hour_volume, four_hour_volume) >= min_volume_ratio
        and min(one_hour_volume, four_hour_volume) >= Decimal("0.70")
    )
    price_in_entry = entry_low <= price <= entry_high
    fake_breakout = any(bool(item.get("fake_breakout")) for item in required_analyses)
    extension = as_decimal(primary.get("extension_from_ema20_atr"))
    late_entry = price > entry_high or extension > Decimal("1.50")
    atr_percent = as_decimal(primary.get("atr_percent"))
    volatility_acceptable = atr_percent <= Decimal("7")
    resistance_blocks_target = (
        resistance > price
        and resistance < target_1
        and not bool(primary.get("breakout_confirmed"))
    )
    market_favorable = bool((market_context or {}).get("favorable", False))
    liquidity_confirmed = bool((market_context or {}).get("symbol_liquid", False))
    daily_change_percent = abs(
        as_decimal((market_context or {}).get("symbol_price_change_percent"))
    )
    daily_move_acceptable = daily_change_percent <= max_daily_move_percent

    gates = {
        "required_timeframes_present": all_required_present,
        "higher_timeframes_bullish": higher_timeframes_bullish,
        "one_hour_entry_ready": one_hour_entry_ready,
        "no_required_timeframe_bearish": no_required_bearish,
        "combined_score": combined >= min_combined_score,
        "confidence": confidence >= min_confidence_percent,
        "volume_confirmed": volume_confirmed,
        "price_inside_entry": price_in_entry,
        "liquidity_confirmed": liquidity_confirmed,
        "daily_move_acceptable": daily_move_acceptable,
        "market_favorable": market_favorable,
        "no_fake_breakout": not fake_breakout,
        "not_late_entry": not late_entry,
        "volatility_acceptable": volatility_acceptable,
        "stop_risk": Decimal("0") < stop_risk_percent <= max_stop_risk_percent,
        "reward_risk": actual_reward_risk >= min_reward_risk,
        "resistance_clear": not resistance_blocks_target,
    }
    strong_setup = all(gates.values())
    if strong_setup:
        decision = "BUY_SETUP"
    elif combined <= Decimal("-0.42") and position_held:
        decision = "SELL_REVIEW"
    else:
        decision = "WAIT"

    levels: dict[str, str | None] = {
        "entry_low": None,
        "entry_high": None,
        "stop_loss": None,
        "target_1": None,
        "target_2": None,
        "suggested_allocation_usdt": None,
    }
    if price > 0 and unit_risk > 0:
        suggested_allocation: Decimal | None = None
        if decision == "BUY_SETUP":
            risk_budget = account_value_usdt * (risk_percent / Decimal("100"))
            risk_position_value = (risk_budget / unit_risk) * price
            allocation_cap = free_usdt * (max_allocation_percent / Decimal("100"))
            suggested_allocation = min(risk_position_value, allocation_cap)
        levels = {
            "entry_low": decimal_text(entry_low),
            "entry_high": decimal_text(entry_high),
            "stop_loss": decimal_text(stop_loss),
            "target_1": decimal_text(target_1),
            "target_2": decimal_text(target_2),
            "suggested_allocation_usdt": (
                format(max(suggested_allocation, Decimal("0")), ".2f")
                if suggested_allocation is not None
                else None
            ),
        }

    bullish = sum(1 for item in analyses if item.get("trend") == "BULLISH")
    bearish = sum(1 for item in analyses if item.get("trend") == "BEARISH")
    explanation = [
        f"{bullish} timeframe(s) bullish and {bearish} bearish.",
        f"Weighted technical score: {combined:.3f}.",
    ]
    if decision == "BUY_SETUP":
        explanation.append("All strong-setup quality gates passed; execution remains manual.")
    elif decision == "SELL_REVIEW":
        explanation.append("Downside alignment reached the review threshold; do not add automatically.")
    else:
        failed = [name for name, passed in gates.items() if not passed]
        explanation.append("Strong setup rejected: " + ", ".join(failed) + ".")

    return {
        "symbol": symbol,
        "decision": decision,
        "confidence_percent": format(confidence, ".0f"),
        "combined_score": format(combined, ".3f"),
        "current_price": decimal_text(price),
        "levels": levels,
        "explanation": explanation,
        "timeframes": analyses,
        "quality_gates": gates,
        "market_context": market_context or {},
        "support_resistance": {
            "support": decimal_text(support),
            "resistance": decimal_text(resistance),
            "fake_breakout": fake_breakout,
            "late_entry": late_entry,
            "resistance_blocks_target_1": resistance_blocks_target,
        },
        "risk": {
            "risk_percent": format(risk_percent, ".2f"),
            "stop_risk_percent": format(stop_risk_percent, ".2f"),
            "reward_risk_target_1": format(actual_reward_risk, ".2f"),
            "max_free_usdt_allocation_percent": format(max_allocation_percent, ".2f"),
            "manual_execution_required": True,
            "position_held": position_held,
        },
    }
