"""Análise de cenário e gestão de risco do IronDome.

Entrada: saldo (R$), objetivo (R$), histórico de crashes, calibração IA, config atual.
Saída: dict com diagnóstico + recomendações específicas + config sugerida.
"""
from datetime import datetime, timedelta, timezone


KELLY_FRACTION = 0.25     # 1/4 Kelly — conservador
MAX_STAKE_PCT = 0.05      # nunca mais que 5% do saldo
MIN_STAKE = 1.00          # piso operacional (limite da plataforma)
MAX_BETS_PER_HOUR = 60    # ~1 bet por minuto, sustentável

# targets candidatos pra avaliação
CANDIDATE_TARGETS = [1.5, 2.0, 3.0, 4.0, 5.0, 7.0, 10.0]

# margem por critério IA (acima do break-even pra IA aprovar)
CRITERIO_MARGIN = {'conservador': 0.15, 'equilibrado': 0.10, 'agressivo': 0.05}


def _hit_rate(values: list[float], target: float) -> float:
    if not values: return 0.0
    return sum(1 for v in values if v >= target) / len(values)


def _hour_hit_rate(rows: list[dict], hour: int, target: float) -> float | None:
    """rows = [{ts, value}]; retorna hit rate na hora SP, ou None se amostra < 30."""
    sao = timezone(timedelta(hours=-3))
    matched = [r for r in rows if datetime.fromtimestamp(r.get('ts') or r.get('at', 0), tz=sao).hour == hour]
    if len(matched) < 30: return None
    return _hit_rate([r['value'] for r in matched], target)


def _classify_target(values: list[float], hour_rate: float | None, target: float) -> dict:
    """Avalia um target: hit rate da janela 300, hit rate da hora, edge. Score combinado."""
    rate_300 = _hit_rate(values, target)
    be = 1.0 / target
    # peso 60% hora, 40% janela 300 (se hora estiver disponível)
    if hour_rate is not None:
        rate_blended = hour_rate * 0.6 + rate_300 * 0.4
    else:
        rate_blended = rate_300
    edge = rate_blended - be
    return {
        'target': target,
        'be': be,
        'rate_300': rate_300,
        'rate_hour': hour_rate,
        'rate_blended': rate_blended,
        'edge': edge,
        'edge_pp': edge * 100,
    }


def _kelly_stake(bankroll: float, target: float, hit_rate: float, fraction: float = KELLY_FRACTION) -> float:
    """Kelly clássico pra binário: f = (b*p - (1-p)) / b, onde b = target-1.
    Aplica fraction (default 1/4) e clampa em 0+."""
    b = target - 1.0
    if b <= 0 or hit_rate <= 0: return 0.0
    p = hit_rate
    q = 1.0 - p
    f = (b * p - q) / b  # fração ótima do bankroll
    f = max(0.0, f) * fraction
    return bankroll * f


def _viable_stake(target: float, hit_rate: float, target_profit: float, max_bets: int = 100) -> float:
    """Quanto preciso apostar pra atingir o objetivo em <max_bets>?
    Lucro esperado por bet = stake * (target * hit_rate - 1).
    stake = target_profit / (max_bets * (target*p - 1))."""
    ev_per_unit = (target * hit_rate - 1.0)
    if ev_per_unit <= 0: return 0.0  # EV negativo: não há stake viável
    return target_profit / (max_bets * ev_per_unit)


def _apply_stake_clamps(raw_stake: float, bankroll: float) -> float:
    """Aplica os 3 clamps: teto % saldo, min stake. Arredonda em 0.5."""
    ceiling = bankroll * MAX_STAKE_PCT
    s = min(raw_stake, ceiling)
    s = max(s, 0.0)
    if s < MIN_STAKE: return MIN_STAKE if raw_stake > 0 else 0.0
    return round(s * 2) / 2  # múltiplo de 0.50


def analyze(crashes: list[dict],
            bankroll: float,
            target_profit: float,
            current_cfg: dict | None = None,
            ia_calibration: list[dict] | None = None,
            now_dt: datetime | None = None) -> dict:
    """Executa análise completa. Retorna dict com diagnóstico + recommendations.

    crashes: lista [{ts, value}] ordenada do mais antigo→novo (use só os últimos 300 idealmente)
    bankroll: saldo atual em R$
    target_profit: objetivo de ganho em R$
    current_cfg: config do autoclick (panels, stopLoss, etc) — usado pra diff
    ia_calibration: bands [{band, hit_rate, avg_prob, total, pnl}] — opcional
    now_dt: data/hora atual (default = agora SP)
    """
    sao = timezone(timedelta(hours=-3))
    now_dt = now_dt or datetime.now(sao)
    hr = now_dt.hour

    crashes = crashes[-300:]
    values = [c['value'] for c in crashes]
    n = len(values)

    # ---- avaliação por target ----
    targets_analysis = []
    for T in CANDIDATE_TARGETS:
        hr_rate = _hour_hit_rate(crashes, hr, T)
        targets_analysis.append(_classify_target(values, hr_rate, T))

    # ranking: ordena por edge desc, mas empata com hit rate (preferir mais consistente)
    targets_analysis.sort(key=lambda t: (-t['edge'], -t['rate_blended']))

    # melhor 1 e 2 com edge ≥ 0
    profitable = [t for t in targets_analysis if t['edge'] >= -0.01]  # tolera -1pp
    best_1 = profitable[0] if profitable else targets_analysis[0]
    best_2 = profitable[1] if len(profitable) > 1 else None

    # ---- recomendação de stake por painel ----
    def _stake_for(t):
        if t is None: return 0.0
        kelly = _kelly_stake(bankroll, t['target'], max(t['rate_blended'], 0.01))
        viable = _viable_stake(t['target'], max(t['rate_blended'], 0.01), target_profit)
        # menor dos dois (Kelly e viável). Se algum é zero (EV negativo), pula
        candidates = [s for s in [kelly, viable] if s > 0]
        if not candidates:
            return 0.0
        raw = min(candidates)
        return _apply_stake_clamps(raw, bankroll)

    p1_stake = _stake_for(best_1)
    p2_stake = _stake_for(best_2) if best_2 else 0.0

    # ---- stop-loss / stop-win ----
    # stop-loss = 30% do saldo OR 1.5x objetivo (o menor)
    stop_loss_rec = round(min(bankroll * 0.30, target_profit * 1.5), 2)
    # stop-win = objetivo (encerra ao atingir)
    stop_win_rec = round(target_profit, 2)

    # max loss streak: 3 (default IronDome)
    max_streak_rec = 3

    # ---- IA criterio baseado em calibração ----
    criterio_rec = 'equilibrado'
    ia_diagnosis = []
    if ia_calibration:
        # se IA está consistentemente OVER (gap negativo > 10pp em ≥2 faixas), apertar
        over_count = sum(1 for b in ia_calibration if (b.get('hit_rate', 0) - b.get('avg_prob', 0)) < -10 and b.get('total', 0) >= 5)
        if over_count >= 2:
            criterio_rec = 'conservador'
            ia_diagnosis.append(f'IA over em {over_count} faixas → apertar pra Conservador (+15% margem)')
        elif over_count == 1:
            ia_diagnosis.append('IA marginalmente over numa faixa → manter Equilibrado')
        else:
            ia_diagnosis.append('IA calibrada — manter Equilibrado')

    # ---- painéis ativos ----
    # Painel ativo se edge tolerável E stake calculado > 0 (Kelly/viable não-zero).
    # Se stake é 0 mas edge é tolerável, força MIN_STAKE (não permitimos R$ 0).
    p1_active = best_1['edge'] > -0.005 and p1_stake > 0
    p2_active = best_2 is not None and best_2['edge'] > -0.01 and p2_stake > 0

    # garantir piso de stake quando ativo (nunca recomendar R$ 0)
    p1_stake_final = max(p1_stake, MIN_STAKE) if p1_active else MIN_STAKE
    p2_stake_final = max(p2_stake, MIN_STAKE) if p2_active else MIN_STAKE

    # ---- monta recomendação ----
    recommended = {
        'panels': [
            {
                'enabled': p1_active,
                'cashoutTarget': best_1['target'],
                'betAmount': p1_stake_final,
                'operationMode': criterio_rec,
            },
            {
                'enabled': p2_active,
                'cashoutTarget': best_2['target'] if best_2 else 2.0,
                'betAmount': p2_stake_final,
                'operationMode': criterio_rec,
            },
        ],
        'stopLoss': stop_loss_rec,
        'stopWin': stop_win_rec,
        'maxLossStreak': max_streak_rec,
        'operationMode': criterio_rec,
    }

    # ---- diagnóstico narrativo ----
    diagnosis = {
        'sample_size': n,
        'bankroll': bankroll,
        'target_profit': target_profit,
        'hour': hr,
        'targets_ranking': targets_analysis,
        'best_targets': [best_1] + ([best_2] if best_2 else []),
        'kelly_per_panel': [
            {'target': best_1['target'], 'stake': p1_stake},
            {'target': best_2['target'] if best_2 else None, 'stake': p2_stake} if best_2 else None,
        ],
        'ia_diagnosis': ia_diagnosis,
        'now': now_dt.isoformat(),
    }

    # ---- diff vs config atual ----
    changes = []
    if current_cfg:
        cur_panels = current_cfg.get('panels') or [{}, {}]
        for i in (0, 1):
            cur = cur_panels[i] if i < len(cur_panels) else {}
            rec = recommended['panels'][i]
            for k in ('enabled', 'cashoutTarget', 'betAmount', 'operationMode'):
                cv = cur.get(k)
                rv = rec.get(k)
                if cv != rv and not (isinstance(cv, float) and isinstance(rv, float) and abs(cv - rv) < 0.01):
                    changes.append({'path': f'panels[{i}].{k}', 'from': cv, 'to': rv,
                                    'panel': i + 1, 'field': k})
        for k in ('stopLoss', 'stopWin', 'maxLossStreak'):
            cv = current_cfg.get(k)
            rv = recommended[k]
            if cv != rv and not (isinstance(cv, (int, float)) and isinstance(rv, (int, float)) and abs(float(cv) - float(rv)) < 0.01):
                changes.append({'path': k, 'from': cv, 'to': rv, 'field': k})

    # ---- significância: critério mínimo pra dispar popup (texto em PT) ----
    def _yn(v): return 'Sim' if v else 'Não'
    def _mode(v): return {'conservador': 'Conservador', 'equilibrado': 'Equilibrado', 'agressivo': 'Agressivo'}.get(v, v)
    significant = False
    sig_reasons = []
    for ch in changes:
        f = ch.get('field')
        pn = ch.get('panel')
        pref = f'Painel {pn}' if pn else ''
        if f == 'enabled':
            significant = True
            sig_reasons.append(f'{pref} {"ativar" if ch["to"] else "desativar"} ({_yn(ch["from"])} → {_yn(ch["to"])})')
        elif f == 'cashoutTarget':
            significant = True
            sig_reasons.append(f'{pref} — alvo (target): {ch["from"]}x → {ch["to"]}x')
        elif f == 'operationMode':
            if ch['from'] != ch['to']:
                significant = True
                sig_reasons.append(f'{pref} — modo IA: {_mode(ch["from"])} → {_mode(ch["to"])}'.strip())
        elif f == 'betAmount':
            cur = float(ch['from'] or 0); new = float(ch['to'] or 0)
            if (cur > 0 and abs(new - cur) / cur > 0.5) or (cur == 0 and new > 0):
                significant = True
                sig_reasons.append(f'{pref} — valor da aposta: R${cur:.2f} → R${new:.2f}')
        elif f == 'stopLoss':
            cur = float(ch['from'] or 0); new = float(ch['to'] or 0)
            if (cur > 0 and abs(new - cur) / cur > 0.30) or (cur == 0 and new > 0):
                significant = True
                sig_reasons.append(f'Stop-loss diário: R${cur:.2f} → R${new:.2f}')
        elif f == 'stopWin':
            cur = float(ch['from'] or 0); new = float(ch['to'] or 0)
            if (cur > 0 and abs(new - cur) / cur > 0.30) or (cur == 0 and new > 0):
                significant = True
                sig_reasons.append(f'Stop-win diário: R${cur:.2f} → R${new:.2f}')
        elif f == 'maxLossStreak':
            if ch['from'] != ch['to']:
                significant = True
                sig_reasons.append(f'Parada após perdas seguidas: {ch["from"]} → {ch["to"]}')
        elif f == 'operationMode' and pn is None:
            sig_reasons.append(f'Critério IA global: {_mode(ch["from"])} → {_mode(ch["to"])}')

    return {
        'diagnosis': diagnosis,
        'recommended': recommended,
        'changes': changes,
        'significant': significant,
        'significance_reasons': sig_reasons,
    }
