"""
Полный спекулятивный технический анализ ВСЕХ тикеров из базы данных MySQL.

Источники данных:
  - MySQL (nlbotinterface.ru:3306 / bitcoin_tickers) — история OHLCV H1/D1/W1
  - T-Bank Invest API (real, только чтение) — текущие цены + биржевой стакан

Для каждого тикера:
  1. Загрузка H1/D1/W1 из БД
  2. Расчёт индикаторов (EMA/SMA/MACD/RSI/Stoch/BB/ATR/ADX/CCI/OBV)
  3. Свечные паттерны + VSA-сигналы
  4. Multi-Timeframe тренды (W1=50% / D1=30% / H1=20%)
  5. Фаза Вайкоффа (Accumulation/Markdown/Markup/Distribution)
  6. Ключевые уровни S/R
  7. T-Bank OrderBook (дисбаланс bid/ask, стены) — для MOEX-тикеров
  8. Генерация сигнала BUY/SELL/HOLD с confidence-оценкой

Запуск:
    python _analyze_all_db.py
"""
import sys
import os
import json
import warnings
from datetime import datetime

sys.path.insert(0, os.path.dirname(__file__))
warnings.filterwarnings("ignore")

import pandas as pd
import numpy as np

from src.db.connection import fetch_ohlcv, get_available_tickers
from src.indicators.calculations import calc_all_indicators
from src.analysis.tech_analysis import (
    detect_candlestick_patterns,
    detect_vsa_signals,
    find_key_levels,
    determine_trend,
    wyckoff_phase,
    generate_trade_signal,
)
from src.api.tbank import (
    get_last_price,
    get_order_book,
    analyze_order_book,
    set_tbank_env,
    _find_figi,
)

# Тикеры, которые относятся к MOEX (остальные в БД — крипто/FOREX)
MOEX_TICKERS_SET = {
    'SBER', 'GAZP', 'LKOH', 'ROSN', 'NVTK', 'MGNT', 'TATN', 'SNGS', 'SNGSP',
    'PLZL', 'PHOR', 'NLMK', 'CHMF', 'GMKN', 'ALRS', 'MTSS', 'VTBR', 'MOEX',
    'FIVE', 'X5', 'AFLT', 'POLY', 'RUAL', 'SELG', 'IRAO', 'HYDR', 'MAGN',
    'RASP', 'NMTP', 'FESH', 'CBOM', 'TCSG', 'VKCO', 'YNDX', 'OZON', 'ASTR',
    # Привилегированные акции
    'SBERP', 'TATNP',
}


def classify_ticker(ticker: str) -> str:
    """Классификация тикера: MOEX / CRYPTO / FOREX / UNKNOWN."""
    t = ticker.upper()
    if t in MOEX_TICKERS_SET:
        return 'MOEX'
    if 'BITCOIN' in t or 'BTC' in t or 'ETH' in t:
        return 'CRYPTO'
    if 'USD' in t or 'EUR' in t or 'RUB' in t or 'JPY' in t:
        return 'FOREX'
    return 'UNKNOWN'


def safe_float(val, default=0.0):
    """Безопасное преобразование в float."""
    try:
        if val is None or (isinstance(val, float) and np.isnan(val)):
            return default
        return float(val)
    except (TypeError, ValueError):
        return default


def analyze_ticker(ticker: str, use_real_api: bool = True) -> dict:
    """
    Полный спекулятивный анализ одного тикера.

    Args:
        ticker: тикер.
        use_real_api: использовать real T-Bank API для актуальных цен/стакана.

    Returns:
        словарь с результатами анализа.
    """
    result = {
        'ticker': ticker,
        'class': classify_ticker(ticker),
        'error': None,
        'has_db_data': False,
        'tbank_price': None,
        'trend_w1': '—', 'trend_d1': '—', 'trend_h1': '—',
        'adx_w1': None, 'adx_d1': None, 'adx_h1': None,
        'rsi_d1': None, 'rsi_h1': None,
        'stoch_d1': None, 'stoch_h1': None,
        'cci_d1': None, 'cci_h1': None,
        'macd_d1': None,
        'wyckoff_phase': '—', 'wyckoff_direction': '—',
        'wyckoff_events': [],
        'wyckoff_tr_low': None, 'wyckoff_tr_high': None,
        'signal': 'HOLD', 'confidence': 0,
        'entry': None, 'sl': None, 'tp': None, 'atr': None,
        'reasons': '',
        'levels_support': [],
        'levels_resistance': [],
        'last_h1': None, 'last_d1': None, 'last_w1': None,
        'orderbook': None,
        'candle_d1': '', 'candle_h1': '',
        'vsa_d1': '', 'vsa_h1': '',
    }

    print(f"\n=== {ticker} ({result['class']}) ===")

    # ── 2. Данные из БД (загружаем ПЕРВЫМИ — эталон цены) ──
    try:
        df_h1 = fetch_ohlcv(ticker, 'H1', limit=300)
        df_d1 = fetch_ohlcv(ticker, 'D1', limit=300)
        df_w1 = fetch_ohlcv(ticker, 'W1', limit=150)
    except Exception as e:
        result['error'] = f"DB error: {e}"
        print(f"  DB error: {e}")
        return result

    has_h1 = len(df_h1) > 0
    has_d1 = len(df_d1) > 0
    has_w1 = len(df_w1) > 0
    result['has_db_data'] = has_h1 or has_d1 or has_w1
    print(f"  DB rows: H1={len(df_h1)} D1={len(df_d1)} W1={len(df_w1)}")

    if not (has_h1 or has_d1 or has_w1):
        result['error'] = 'Нет данных в БД'
        return result

    # Эталонная цена из БД (последняя H1, затем D1, затем W1)
    db_price = None
    if has_h1:
        db_price = safe_float(df_h1['Close'].iloc[-1])
    elif has_d1:
        db_price = safe_float(df_d1['Close'].iloc[-1])
    elif has_w1:
        db_price = safe_float(df_w1['Close'].iloc[-1])

    # ── 1. T-Bank API: текущая цена + стакан (только для MOEX) ──
    # ВАЖНО: sanity-check цены T-Bank vs БД. Если расхождение > 25% —
    # считаем FIGI подозрительным (возможно указывает на другой инструмент),
    # отбрасываем T-Bank цену и стакан, используем цену из БД.
    tbank_price = None
    ob_info = None
    has_figi = False
    figi_suspicious = False
    try:
        _find_figi(ticker)
        has_figi = True
    except ValueError:
        has_figi = False

    if has_figi:
        try:
            tbank_price = get_last_price(ticker)
            if tbank_price:
                # Sanity-check vs БД цену
                if db_price and db_price > 0:
                    deviation = abs(tbank_price - db_price) / db_price
                    if deviation > 0.25:
                        print(f"  ⚠ T-Bank price {tbank_price:.4f} отклоняется от БД {db_price:.4f} "
                              f"на {deviation*100:.1f}% — FIGI подозрительный, цена/стакан отброшены")
                        figi_suspicious = True
                        result['figi_warning'] = (
                            f"T-Bank цена {tbank_price:.4f} расходится с БД {db_price:.4f} "
                            f"на {deviation*100:.1f}% — возможно неверный FIGI"
                        )
                        tbank_price = None
                    else:
                        result['tbank_price'] = round(tbank_price, 4)
                        print(f"  T-Bank price: {tbank_price:.4f} (отклонение от БД: {deviation*100:.1f}%)")
        except Exception as e:
            print(f"  T-Bank price error: {e}")

        # Стакан — только если FIGI не подозрительный
        if not figi_suspicious:
            try:
                ob = get_order_book(ticker, depth=20)
                ob_analysis = analyze_order_book(ob, large_order_threshold=3000)
                ob_info = {
                    'bid': round(ob.best_bid, 4),
                    'ask': round(ob.best_ask, 4),
                    'spread_pct': round(ob.spread_pct, 3),
                    'imbalance': round(ob.imbalance_ratio, 2),
                    'total_bid_vol': ob.total_bid_volume,
                    'total_ask_vol': ob.total_ask_volume,
                    'verdict': ob_analysis.verdict,
                    'bid_walls': [(round(p, 2), q) for p, q in ob_analysis.bid_walls[:3]],
                    'ask_walls': [(round(p, 2), q) for p, q in ob_analysis.ask_walls[:3]],
                }
                result['orderbook'] = ob_info
                print(f"  OrderBook: bid={ob.best_bid} ask={ob.best_ask} "
                      f"spread={ob.spread_pct:.3f}% imb={ob.imbalance_ratio:.2f} "
                      f"→ {ob_analysis.verdict}")
            except Exception as e:
                print(f"  OrderBook error: {e}")

    # Текущая цена: приоритет T-Bank (если валидна), затем БД
    current_price = tbank_price if tbank_price else db_price

    if current_price is None or current_price <= 0:
        result['error'] = 'Не удалось определить текущую цену'
        return result

    # ── 3. Расчёт индикаторов ──
    if has_h1 and len(df_h1) >= 20:
        df_h1 = calc_all_indicators(df_h1)
        df_h1 = detect_candlestick_patterns(df_h1)
        df_h1 = detect_vsa_signals(df_h1)
    if has_d1 and len(df_d1) >= 20:
        df_d1 = calc_all_indicators(df_d1)
        df_d1 = detect_candlestick_patterns(df_d1)
        df_d1 = detect_vsa_signals(df_d1)
    if has_w1 and len(df_w1) >= 20:
        df_w1 = calc_all_indicators(df_w1)

    # ── 4. Тренды (Multi-Timeframe) ──
    trend_w1, adx_w1 = 'sideways', 0.0
    trend_d1, adx_d1 = 'sideways', 0.0
    trend_h1, adx_h1 = 'sideways', 0.0

    if has_w1 and len(df_w1) >= 20:
        trend_w1, adx_w1 = determine_trend(df_w1)
        result['trend_w1'] = trend_w1
        result['adx_w1'] = round(safe_float(adx_w1), 1)
    if has_d1 and len(df_d1) >= 20:
        trend_d1, adx_d1 = determine_trend(df_d1)
        result['trend_d1'] = trend_d1
        result['adx_d1'] = round(safe_float(adx_d1), 1)
    if has_h1 and len(df_h1) >= 20:
        trend_h1, adx_h1 = determine_trend(df_h1)
        result['trend_h1'] = trend_h1
        result['adx_h1'] = round(safe_float(adx_h1), 1)

    # ── 5. Осцилляторы (RSI, Stoch, CCI, MACD) ──
    if has_d1 and len(df_d1) >= 20:
        last_d1 = df_d1.iloc[-1]
        result['rsi_d1'] = round(safe_float(last_d1.get('RSI_14')), 1) if 'RSI_14' in df_d1 else None
        result['stoch_d1'] = round(safe_float(last_d1.get('Stoch_K')), 1) if 'Stoch_K' in df_d1 else None
        result['cci_d1'] = round(safe_float(last_d1.get('CCI_20')), 1) if 'CCI_20' in df_d1 else None
        if 'MACD_line' in df_d1:
            result['macd_d1'] = round(safe_float(last_d1.get('MACD_line')), 3)
        result['candle_d1'] = str(last_d1.get('candle_pattern', ''))
        result['vsa_d1'] = str(last_d1.get('VSA_signal', ''))
    if has_h1 and len(df_h1) >= 20:
        last_h1 = df_h1.iloc[-1]
        result['rsi_h1'] = round(safe_float(last_h1.get('RSI_14')), 1) if 'RSI_14' in df_h1 else None
        result['stoch_h1'] = round(safe_float(last_h1.get('Stoch_K')), 1) if 'Stoch_K' in df_h1 else None
        result['cci_h1'] = round(safe_float(last_h1.get('CCI_20')), 1) if 'CCI_20' in df_h1 else None
        result['candle_h1'] = str(last_h1.get('candle_pattern', ''))
        result['vsa_h1'] = str(last_h1.get('VSA_signal', ''))

    # ── 6. Фаза Вайкоффа (на W1) ──
    if has_w1 and len(df_w1) >= 20:
        wyck = wyckoff_phase(df_w1)
        result['wyckoff_phase'] = wyck['phase']
        result['wyckoff_direction'] = wyck.get('direction', '—')
        result['wyckoff_events'] = wyck.get('events', [])
        result['wyckoff_tr_low'] = wyck.get('tr_low')
        result['wyckoff_tr_high'] = wyck.get('tr_high')

    # ── 7. Ключевые уровни S/R ──
    levels_h1 = find_key_levels(df_h1) if has_h1 and len(df_h1) >= 20 else {'support': [], 'resistance': []}
    levels_d1 = find_key_levels(df_d1) if has_d1 and len(df_d1) >= 20 else {'support': [], 'resistance': []}

    all_support = list(set(levels_h1.get('support', []) + levels_d1.get('support', [])))
    all_resistance = list(set(levels_h1.get('resistance', []) + levels_d1.get('resistance', [])))
    if current_price:
        all_support = sorted([x for x in all_support if x < current_price], reverse=True)[:5]
        all_resistance = sorted([x for x in all_resistance if x > current_price])[:5]
    result['levels_support'] = [round(x, 2) for x in all_support]
    result['levels_resistance'] = [round(x, 2) for x in all_resistance]

    # ── 8. Последние свечи ──
    if has_h1 and len(df_h1) > 0:
        last = df_h1.iloc[-1]
        result['last_h1'] = {
            'date': str(last.get('Date', '')),
            'time': str(last.get('Time', '')),
            'open': round(safe_float(last['Open']), 4),
            'high': round(safe_float(last['High']), 4),
            'low': round(safe_float(last['Low']), 4),
            'close': round(safe_float(last['Close']), 4),
            'volume': int(safe_float(last.get('Volume', 0))),
        }
    if has_d1 and len(df_d1) > 0:
        last = df_d1.iloc[-1]
        result['last_d1'] = {
            'date': str(last.get('Date', '')),
            'open': round(safe_float(last['Open']), 4),
            'high': round(safe_float(last['High']), 4),
            'low': round(safe_float(last['Low']), 4),
            'close': round(safe_float(last['Close']), 4),
            'volume': int(safe_float(last.get('Volume', 0))),
        }
    if has_w1 and len(df_w1) > 0:
        last = df_w1.iloc[-1]
        result['last_w1'] = {
            'date': str(last.get('Date', '')),
            'open': round(safe_float(last['Open']), 4),
            'high': round(safe_float(last['High']), 4),
            'low': round(safe_float(last['Low']), 4),
            'close': round(safe_float(last['Close']), 4),
            'volume': int(safe_float(last.get('Volume', 0))),
        }

    # ── 9. Генерация сигнала ──
    # Multi-Timeframe: РЕШЕНИЕ на W1+D1, H1 — подтверждение + уточнение TP/SL.
    # levels_h1 — для SL (точный вход), levels_d1 — для TP (цель свинга).
    if current_price and (has_h1 or has_d1):
        # Если нет H1, используем D1 как замену
        df_h1_for_signal = df_h1 if has_h1 and len(df_h1) >= 20 else df_d1
        df_d1_for_signal = df_d1 if has_d1 and len(df_d1) >= 20 else df_h1

        try:
            signal = generate_trade_signal(
                trend_w1=trend_w1,
                trend_d1=trend_d1,
                trend_h1=trend_h1,
                phase=result['wyckoff_phase'],
                levels={'support': all_support, 'resistance': all_resistance},
                df_d1=df_d1_for_signal,
                df_h1=df_h1_for_signal,
                current_price=current_price,
                levels_h1=levels_h1,
                levels_d1=levels_d1,
            )
            result['signal'] = signal['signal']
            result['confidence'] = signal['confidence']
            result['entry'] = signal['entry']
            result['sl'] = signal['sl']
            result['tp'] = signal['tp']
            result['reasons'] = signal['reason']
            result['atr'] = signal.get('atr', 0)
        except Exception as e:
            result['error'] = f"Signal error: {e}"
            print(f"  Signal error: {e}")

    # ── 10. Корректировка confidence по стакану ──
    if ob_info and result['signal'] != 'HOLD':
        ob_verdict = ob_info['verdict']
        reasons = str(result.get('reasons', ''))
        if 'БЫЧИЙ' in ob_verdict and result['signal'] == 'BUY':
            result['confidence'] = min(90, result['confidence'] + 10)
            result['reasons'] = reasons + '; OrderBook бычий bias +10%'
        elif 'МЕДВЕЖИЙ' in ob_verdict and result['signal'] == 'SELL':
            result['confidence'] = min(90, result['confidence'] + 10)
            result['reasons'] = reasons + '; OrderBook медвежий bias +10%'
        elif 'БЫЧИЙ' in ob_verdict and result['signal'] == 'SELL':
            result['confidence'] = max(0, result['confidence'] - 10)
            result['reasons'] = reasons + '; ⚠ OrderBook бычий bias противоречит SELL (-10%)'
        elif 'МЕДВЕЖИЙ' in ob_verdict and result['signal'] == 'BUY':
            result['confidence'] = max(0, result['confidence'] - 10)
            result['reasons'] = reasons + '; ⚠ OrderBook медвежий bias противоречит BUY (-10%)'

    # ── 11. RR (reward:risk) ──
    if result['signal'] != 'HOLD' and result['entry'] and result['sl'] and result['tp']:
        risk = abs(result['entry'] - result['sl'])
        reward = abs(result['tp'] - result['entry'])
        result['rr'] = round(reward / risk, 2) if risk > 0 else None
    else:
        result['rr'] = None

    # ── 12. RR-фильтр (финальный guard): если RR < 2, confidence < 50% ──
    # Применяется ПОСЛЕ корректировки по стакану, чтобы bid/ask-дисбаланс
    # не мог "восстановить" confidence выше 50% для экономически невыгодных
    # сделок (RR < 2 — риск больше половины потенциальной прибыли).
    if (result['rr'] is not None and result['rr'] < 2.0
            and result['confidence'] >= 50):
        result['confidence'] = 49
        reasons = str(result.get('reasons', ''))
        if 'RR<2' not in reasons:
            result['reasons'] = reasons + '; ⚠ RR<2 → conf ограничен 49% (финальный guard)'

    sig_str = result['signal']
    conf = result['confidence']
    print(f"  → {sig_str} (conf: {conf}%) | W1:{result['trend_w1']} D1:{result['trend_d1']} H1:{result['trend_h1']}")
    if result['wyckoff_phase'] != '—':
        print(f"    Wyckoff: {result['wyckoff_phase']}")
    if result['rr']:
        print(f"    Entry={result['entry']} SL={result['sl']} TP={result['tp']} RR=1:{result['rr']}")

    return result


def main():
    print("=" * 72)
    print("ПОЛНЫЙ СПЕКУЛЯТИВНЫЙ АНАЛИЗ ВСЕХ ТИКЕРОВ ИЗ БД (MySQL + T-Bank API)")
    print(f"Дата: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print("=" * 72)

    # Переключаемся на real API для актуальных рыночных данных (только чтение)
    print("\nПереключение T-Bank API → real (только чтение цен/стакана)...")
    try:
        set_tbank_env('real')
    except Exception as e:
        print(f"  Не удалось переключиться на real: {e}. Используется sandbox.")

    # Получаем список тикеров из БД
    print("\nПолучение списка тикеров из БД...")
    tickers = get_available_tickers()
    print(f"Найдено тикеров в БД: {len(tickers)}")
    for t in tickers:
        print(f"  {t:12s} → {classify_ticker(t)}")

    # Анализ каждого тикера
    results = []
    for ticker in tickers:
        try:
            r = analyze_ticker(ticker, use_real_api=True)
            results.append(r)
        except Exception as e:
            print(f"\n=== {ticker} ===\n  КРИТИЧЕСКАЯ ОШИБКА: {e}")
            results.append({'ticker': ticker, 'class': classify_ticker(ticker),
                            'error': str(e), 'signal': 'HOLD', 'confidence': 0})

    # ── Сводка ──
    print(f"\n{'=' * 72}")
    print("СВОДКА РЕЗУЛЬТАТОВ")
    print(f"{'=' * 72}")

    signals = {'BUY': [], 'SELL': [], 'HOLD': []}
    for r in results:
        sig = r.get('signal', 'HOLD')
        if sig not in signals:
            signals[sig] = []
        signals[sig].append(r)

    print(f"\nBUY: {len(signals['BUY'])} | SELL: {len(signals['SELL'])} | HOLD: {len(signals['HOLD'])}")

    for sig_type in ['BUY', 'SELL']:
        tickers_sig = sorted(signals[sig_type], key=lambda x: x.get('confidence', 0), reverse=True)
        if tickers_sig:
            print(f"\n--- {sig_type} Signals ---")
            print(f"{'Тикер':8s} {'Conf':>5s} {'Цена':>10s} {'W1':>10s} {'D1':>10s} {'H1':>10s} {'Wyckoff':30s} {'OB':18s} {'RR':>6s}")
            for r in tickers_sig:
                conf = r.get('confidence', 0)
                price = r.get('tbank_price') or r.get('entry') or 0
                ob_v = r.get('orderbook', {}).get('verdict', '—') if r.get('orderbook') else 'НЕТ'
                wyck = (r.get('wyckoff_phase', '—') or '—')[:28]
                rr = r.get('rr')
                rr_str = f"1:{rr}" if rr else '—'
                print(f"{r['ticker']:8s} {conf:4.0f}% {price:>10.2f} "
                      f"{r.get('trend_w1','—'):>10s} {r.get('trend_d1','—'):>10s} "
                      f"{r.get('trend_h1','—'):>10s} {wyck:30s} {ob_v:18s} {rr_str:>6s}")

    # HOLD
    if signals['HOLD']:
        print(f"\n--- HOLD ({len(signals['HOLD'])}) ---")
        for r in signals['HOLD']:
            price = r.get('tbank_price') or r.get('entry') or 0
            err = r.get('error', '')
            err_str = f" [err: {err}]" if err else ''
            print(f"  {r['ticker']:8s} ({r.get('class','?'):6s}) price={price:>10.2f} "
                  f"W1:{r.get('trend_w1','—')} D1:{r.get('trend_d1','—')} H1:{r.get('trend_h1','—')}{err_str}")

    # Сохранение результатов
    output = {
        'timestamp': datetime.now().isoformat(),
        'env': 'real',
        'total_tickers': len(results),
        'summary': {
            'BUY': len(signals['BUY']),
            'SELL': len(signals['SELL']),
            'HOLD': len(signals['HOLD']),
        },
        'results': results,
    }
    out_path = '/tmp/moex_full_analysis_results.json'
    with open(out_path, 'w', encoding='utf-8') as f:
        json.dump(output, f, indent=2, ensure_ascii=False, default=str)
    print(f"\nРезультаты сохранены в {out_path}")

    return results


if __name__ == '__main__':
    main()
