"""
Adaptive Threshold Manager (v12.8+)

Динамическая корректировка порогов сигналов на основе:
- Rolling WinRate последних N сделок
- Рыночного режима (choppiness)

Каждый тикер отслеживается независимо.

Formula:
    adjustment = -(winrate - breakeven) * max_adjustment / max(1-breakeven, breakeven)
    
    At WR=60%: adj = -0.04 → threshold = base - 0.04 (легче войти)
    At WR=40%: adj =  0.0 → threshold = base (без изменений)
    At WR=20%: adj = +0.04 → threshold = base + 0.04 (сложнее войти)
"""

import json
import os
import logging
from typing import Optional

logger = logging.getLogger('AI_Strategy')

STATE_FILE = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
    'models', 'adaptive_state.json'
)

ADAPTIVE_CONFIG = {
    'enabled': True,
    'window_size': 20,           # rolling window для WinRate
    'min_trades': 5,             # мин. сделок до начала адаптации
    'max_adjustment': 0.04,      # макс. коррекция порога
    'wr_breakeven': 0.40,        # WR при которой adj=0 (точка безубытка RR 1:2)
    'choppiness_up_adj': 0.02,   # добавка в боковике (труднее войти)
    'trend_adj': 0.02,           # вычет в тренде (легче войти)
    'clamp_min': 0.45,
    'clamp_max': 0.75,
    'persistence_file': STATE_FILE,
}


class AdaptiveThresholdManager:
    """Per-ticker adaptive threshold based on rolling trade outcomes."""

    def __init__(self, config: dict | None = None):
        self.config = {**ADAPTIVE_CONFIG, **(config or {})}
        self._state: dict[str, dict] = {}
        self._load()

    # ── Persistence ────────────────────────────────────────────────

    def _state_path(self) -> str:
        return self.config.get('persistence_file', STATE_FILE)

    def _load(self):
        path = self._state_path()
        if os.path.exists(path):
            try:
                with open(path, 'r') as f:
                    self._state = json.load(f)
                logger.info(f"AdaptiveThreshold: loaded {len(self._state)} tickers from {path}")
            except Exception as e:
                logger.warning(f"AdaptiveThreshold: failed to load {path}: {e}")
                self._state = {}

    def _save(self):
        path = self._state_path()
        try:
            os.makedirs(os.path.dirname(path), exist_ok=True)
            with open(path, 'w') as f:
                json.dump(self._state, f, indent=2)
        except Exception as e:
            logger.warning(f"AdaptiveThreshold: failed to save {path}: {e}")

    # ── Recording ──────────────────────────────────────────────────

    def record_outcome(self, ticker: str, won: bool):
        """Records TP (True) or SL (False) outcome for a ticker."""
        ticker = ticker.upper()
        if ticker not in self._state:
            self._state[ticker] = {'wins': 0, 'losses': 0, 'outcomes': []}

        state = self._state[ticker]
        state['outcomes'].append(1 if won else 0)
        if won:
            state['wins'] += 1
        else:
            state['losses'] += 1

        # Rolling window trim
        max_w = max(self.config['window_size'], 1)
        while len(state['outcomes']) > max_w:
            removed = state['outcomes'].pop(0)
            if removed:
                state['wins'] -= 1
            else:
                state['losses'] -= 1

        self._save()

    def record_outcomes_batch(self, ticker: str, outcomes: list[bool]):
        """Records multiple outcomes at once (e.g., all ladder tiers)."""
        for won in outcomes:
            self.record_outcome(ticker, won)

    # ── Query ──────────────────────────────────────────────────────

    def get_winrate(self, ticker: str) -> Optional[float]:
        """Rolling win rate, None if insufficient data."""
        ticker = ticker.upper()
        state = self._state.get(ticker)
        if not state:
            return None
        total = state['wins'] + state['losses']
        if total < self.config['min_trades']:
            return None
        return state['wins'] / total if total > 0 else None

    def get_adjustment(self, ticker: str,
                       choppiness: Optional[float] = None) -> float:
        """
        Computes threshold adjustment:
          Positive → harder to trigger (threshold UP)
          Negative → easier to trigger (threshold DOWN)
        """
        if not self.config.get('enabled', True):
            return 0.0

        wr = self.get_winrate(ticker)
        if wr is None:
            return 0.0

        max_adj = self.config['max_adjustment']
        be = self.config['wr_breakeven']

        # Linear: WR above breakeven → negative adj (easier)
        if wr > be:
            adj = -(wr - be) / (1.0 - be) * max_adj
        else:
            adj = -(wr - be) / be * max_adj

        adj = max(-max_adj, min(max_adj, adj))

        # Regime overlay
        if choppiness is not None:
            if choppiness > 61.8:
                adj += self.config['choppiness_up_adj']
            elif choppiness < 38.2:
                adj -= self.config['trend_adj']

        return round(adj, 4)

    def get_adjusted_threshold(self, ticker: str,
                               base_threshold: float,
                               choppiness: Optional[float] = None) -> float:
        """Returns adjusted threshold, clamped to [clamp_min, clamp_max]."""
        adj = self.get_adjustment(ticker, choppiness)
        adjusted = base_threshold + adj
        adjusted = max(self.config['clamp_min'],
                       min(self.config['clamp_max'], adjusted))
        return round(adjusted, 4)

    # ── Diagnostics ────────────────────────────────────────────────

    def get_state(self, ticker: str) -> dict:
        ticker = ticker.upper()
        s = self._state.get(ticker, {})
        wr = self.get_winrate(ticker)
        return {
            'wins': s.get('wins', 0),
            'losses': s.get('losses', 0),
            'total': s.get('wins', 0) + s.get('losses', 0),
            'winrate': wr,
            'adjustment': self.get_adjustment(ticker),
        }

    def all_states(self) -> dict:
        return dict(self._state)


# ── Singleton helpers ──────────────────────────────────────────────

_instance: Optional[AdaptiveThresholdManager] = None


def get_manager(config: dict | None = None) -> AdaptiveThresholdManager:
    global _instance
    if _instance is None:
        _instance = AdaptiveThresholdManager(config)
    return _instance


def record_trade_outcome(ticker: str, won: bool):
    get_manager().record_outcome(ticker, won)


def adjusted_threshold(ticker: str, base: float,
                       choppiness: Optional[float] = None) -> float:
    return get_manager().get_adjusted_threshold(ticker, base, choppiness)
