"""Shared trading policy and alignment scoring.

The LLM prompt, trigger-state builder, and deterministic research gate must
use one source of truth. Keeping these numbers here avoids prompt/gate drift.
"""
from __future__ import annotations

from dataclasses import asdict, dataclass
from typing import Any

from .models import OrderflowSnapshot


@dataclass(frozen=True)
class TradingPolicy:
    book_imbalance_threshold: float = 0.15
    tape_delta_threshold: float = 0.15
    vwap_near_pct: float = 0.005
    rsi_min: float = 35.0
    rsi_max: float = 65.0
    absorption_warning_threshold: float = 0.50
    absorption_hard_veto_threshold: float = 0.60
    reversal_absorption_threshold: float = 0.30
    fresh_initiation_book_threshold: float = 0.10
    fresh_initiation_delta_threshold: float = 0.15
    trigger_response_delta_threshold: float = 0.20
    trigger_tolerance_factor: float = 0.25
    funding_crowded_threshold: float = 0.001
    min_alignment_score: int = 5
    spread_max_bps: float = 5.0
    min_rr: float = 1.50
    stop_atr_multiplier: float = 1.20
    min_stop_noise_multiple: float = 1.10
    min_target_pct: float = 0.008
    min_target_atr_multiple: float = 2.50
    prefer_htf_targets: bool = True
    enforce_min_target_distance: bool = False
    runner_target_rr: float = 2.50
    max_target_rr: float = 5.00
    runner_min_confidence: float = 0.65
    fee_round_trip_bps: float = 9.0

    @classmethod
    def from_config(cls, cfg: Any) -> "TradingPolicy":
        values = {}
        for field in cls.__dataclass_fields__:
            if hasattr(cfg, field):
                values[field] = getattr(cfg, field)
        if hasattr(cfg, "fee_bps"):
            values.setdefault("fee_round_trip_bps", float(getattr(cfg, "fee_bps")) * 2.0)
        if hasattr(cfg, "spread_max_bps"):
            values.setdefault("spread_max_bps", float(getattr(cfg, "spread_max_bps")))
        if hasattr(cfg, "stop_atr_multiplier"):
            values.setdefault("stop_atr_multiplier", float(getattr(cfg, "stop_atr_multiplier")))
        return cls(**values)

    def as_dict(self) -> dict[str, Any]:
        return asdict(self)

    def prompt_vars(self) -> dict[str, str]:
        return {
            "book_threshold": _fmt(self.book_imbalance_threshold),
            "book_threshold_neg": _fmt(-self.book_imbalance_threshold),
            "tape_threshold": _fmt(self.tape_delta_threshold),
            "tape_threshold_neg": _fmt(-self.tape_delta_threshold),
            "vwap_near_pct": _fmt_pct(self.vwap_near_pct),
            "rsi_min": _fmt(self.rsi_min),
            "rsi_max": _fmt(self.rsi_max),
            "absorption_warning": _fmt(self.absorption_warning_threshold),
            "absorption_hard_veto": _fmt(self.absorption_hard_veto_threshold),
            "reversal_absorption": _fmt(self.reversal_absorption_threshold),
            "funding_threshold": _fmt(self.funding_crowded_threshold),
            "funding_threshold_neg": _fmt(-self.funding_crowded_threshold),
            "min_alignment": str(int(self.min_alignment_score)),
            "spread_max_bps": _fmt(self.spread_max_bps),
            "min_rr": _fmt(self.min_rr),
            "stop_atr_multiplier": _fmt(self.stop_atr_multiplier),
            "min_stop_noise_multiple": _fmt(self.min_stop_noise_multiple),
            "min_target_pct": _fmt_pct(self.min_target_pct),
            "min_target_atr_multiple": _fmt(self.min_target_atr_multiple),
            "prefer_htf_targets": str(bool(self.prefer_htf_targets)).lower(),
            "enforce_min_target_distance": str(bool(self.enforce_min_target_distance)).lower(),
            "runner_target_rr": _fmt(self.runner_target_rr),
            "max_target_rr": _fmt(self.max_target_rr),
            "runner_min_confidence": _fmt(self.runner_min_confidence),
            "fee_round_trip_bps": _fmt(self.fee_round_trip_bps),
        }


def alignment_metrics(of: OrderflowSnapshot, side: str, policy: TradingPolicy) -> list[dict[str, Any]]:
    """Return binary checks plus magnitude for ranking and journaling."""
    side = side.lower()
    if side == "long":
        return [
            _metric("book_bid_heavy", of.book_imbalance, policy.book_imbalance_threshold, "gte"),
            _metric("tape_buy_aggression", of.tape_delta_pct, policy.tape_delta_threshold, "gte"),
            _metric("near_above_vwap", of.price_vs_vwap, -policy.vwap_near_pct, "gte"),
            _metric("macd_bullish", of.macd_histogram, 0.0, "gte"),
            _range_metric("rsi_not_exhausted", of.rsi_14, policy.rsi_min, policy.rsi_max),
            _metric("ask_absorption_below_veto", of.ask_absorption, policy.absorption_hard_veto_threshold, "lte"),
            _metric("funding_not_crowded", of.funding_rate, policy.funding_crowded_threshold, "lte"),
        ]
    return [
        _metric("book_ask_heavy", of.book_imbalance, -policy.book_imbalance_threshold, "lte"),
        _metric("tape_sell_aggression", of.tape_delta_pct, -policy.tape_delta_threshold, "lte"),
        _metric("near_below_vwap", of.price_vs_vwap, policy.vwap_near_pct, "lte"),
        _metric("macd_bearish", of.macd_histogram, 0.0, "lte"),
        _range_metric("rsi_not_exhausted", of.rsi_14, policy.rsi_min, policy.rsi_max),
        _metric("bid_absorption_below_veto", of.bid_absorption, policy.absorption_hard_veto_threshold, "lte"),
        _metric("funding_not_crowded", of.funding_rate, -policy.funding_crowded_threshold, "gte"),
    ]


def alignment_summary(of: OrderflowSnapshot, side: str, policy: TradingPolicy) -> dict[str, Any]:
    metrics = alignment_metrics(of, side, policy)
    passing = sum(1 for item in metrics if item["ok"])
    strength = sum(float(item["strength"]) for item in metrics) / max(1, len(metrics))
    return {
        "side": side,
        "passing": passing,
        "total": len(metrics),
        "score": f"{passing}/{len(metrics)}",
        "strength": round(strength, 3),
        "metrics": metrics,
    }


def format_alignment(of: OrderflowSnapshot, side: str, policy: TradingPolicy) -> str:
    summary = alignment_summary(of, side, policy)
    lines = []
    for item in summary["metrics"]:
        mark = "PASS" if item["ok"] else "FAIL"
        threshold = _format_threshold(item["threshold"])
        lines.append(
            f"    [{mark}] {item['label']}: value={item['value']:.6g} "
            f"threshold={threshold} margin={item['margin']:+.6g} "
            f"strength={item['strength']:.2f}"
        )
    lines.append(f"    Score: {summary['score']} | magnitude={summary['strength']:.2f}")
    return "\n".join(lines)


def true_side_triggers(side: str, trigger_state: dict[str, Any]) -> list[str]:
    suffix = "_long" if side.lower() == "long" else "_short"
    return [key for key, value in (trigger_state or {}).items() if key.endswith(suffix) and value is True]


def has_side_reversal(side: str, trigger_state: dict[str, Any]) -> bool:
    keys = (
        ("sweep_reclaim_long", "absorption_reversal_long", "cvd_divergence_long", "crowding_fade_long")
        if side.lower() == "long"
        else ("sweep_reclaim_short", "absorption_reversal_short", "cvd_divergence_short", "crowding_fade_short")
    )
    return any(bool((trigger_state or {}).get(key)) for key in keys)


def _metric(label: str, value: float, threshold: float, mode: str) -> dict[str, Any]:
    value = float(value or 0.0)
    threshold = float(threshold or 0.0)
    ok = value >= threshold if mode == "gte" else value <= threshold
    margin = value - threshold if mode == "gte" else threshold - value
    denom = max(abs(threshold), 1e-9)
    strength = max(0.0, min(2.0, margin / denom + (1.0 if ok else 0.0)))
    return {
        "label": label,
        "ok": ok,
        "value": value,
        "threshold": threshold,
        "margin": margin,
        "strength": round(strength, 3),
    }


def _range_metric(label: str, value: float, low: float, high: float) -> dict[str, Any]:
    value = float(value or 0.0)
    ok = low <= value <= high
    if ok:
        center = (low + high) / 2.0
        half = max((high - low) / 2.0, 1e-9)
        margin = min(value - low, high - value)
        strength = 1.0 + max(0.0, 1.0 - abs(value - center) / half) * 0.5
    else:
        margin = min(value - low, high - value)
        strength = 0.0
    return {
        "label": label,
        "ok": ok,
        "value": value,
        "threshold": (low, high),
        "margin": margin,
        "strength": round(strength, 3),
    }


def _fmt(value: float) -> str:
    return f"{float(value):.6g}"


def _fmt_pct(value: float) -> str:
    return f"{float(value) * 100:.3g}%"


def _format_threshold(value: Any) -> str:
    if isinstance(value, tuple):
        return f"{value[0]:.6g}..{value[1]:.6g}"
    return f"{float(value):.6g}"
