"""
Сегментный анализ MOEX — группировка тикеров по отраслям.

Адаптировано из Market Analisys project.
Использует нашу data.loader вместо их db.connection.
"""
from typing import Any, Optional

import numpy as np
import pandas as pd

from data.loader import load_dataframe
from features.technical import engineer_features

# ── Сегменты MOEX ──────────────────────────────────────────────────────
# Ключ: название сегмента (англ., lowercase)
# Значение: dict с именем (рус), списком тикеров и описанием
MOEX_SEGMENTS: dict[str, dict[str, Any]] = {
    'banks': {
        'name_ru': 'Банки',
        'tickers': ['SBER', 'VTBR', 'CBOM'],
        'description': 'Банковский сектор: Сбер, ВТБ, МКБ',
    },
    'oil_gas': {
        'name_ru': 'Нефть и Газ',
        'tickers': ['GAZP', 'LKOH', 'ROSN', 'NVTK', 'TATN', 'TATNP', 'SNGS', 'SNGSP'],
        'description': 'Нефтегазовый сектор: Газпром, Лукойл, Роснефть, Новатэк, Татнефть, Сургутнефтегаз',
    },
    'metals_mining': {
        'name_ru': 'Металлы и Добыча',
        'tickers': ['PLZL', 'NLMK', 'CHMF', 'GMKN', 'ALRS', 'MAGN', 'POLY', 'RASP', 'RUAL'],
        'description': 'Металлургия и добыча: Полюс, НЛМК, Северсталь, Норникель, Алроса, ММК, Полиметалл, Распадская, Русал',
    },
    'chemicals': {
        'name_ru': 'Химия и Удобрения',
        'tickers': ['PHOR'],
        'description': 'Химическая промышленность: Фосагро',
    },
    'consumer': {
        'name_ru': 'Потребительский сектор',
        'tickers': ['MGNT', 'X5', 'FIVE'],
        'description': 'Ритейл и потребление: Магнит, Х5, Fix Price (FIVE)',
    },
    'telecom': {
        'name_ru': 'Телекоммуникации',
        'tickers': ['MTSS'],
        'description': 'Телекоммуникации: МТС',
    },
    'utilities': {
        'name_ru': 'Электроэнергетика',
        'tickers': ['IRAO', 'HYDR'],
        'description': 'Электроэнергетика: Интер РАО, РусГидро',
    },
    'transport': {
        'name_ru': 'Транспорт',
        'tickers': ['AFLT', 'NMTP', 'FESH'],
        'description': 'Транспорт: Аэрофлот, НМТП, ДВМП (FESCO)',
    },
    'it': {
        'name_ru': 'ИТ и Интернет',
        'tickers': ['VKCO', 'YNDX', 'OZON', 'ASTR'],
        'description': 'ИТ-сектор: VK, Яндекс, OZON, Астра',
    },
    'finance': {
        'name_ru': 'Финансы',
        'tickers': ['MOEX'],
        'description': 'Финансовый сектор: Московская Биржа',
    },
    'agriculture': {
        'name_ru': 'Сельское хозяйство',
        'tickers': ['SELG'],
        'description': 'Сельское хозяйство: Русагро',
    },
}

# Обратный индекс: тикер → сегмент
_TICKER_TO_SEGMENT: dict[str, str] = {}
for seg_name, seg_data in MOEX_SEGMENTS.items():
    for t in seg_data['tickers']:
        _TICKER_TO_SEGMENT[t.upper()] = seg_name


def get_segment_for_ticker(ticker: str) -> Optional[str]:
    """Определить сегмент для тикера (напр. 'SBER' → 'banks')."""
    return _TICKER_TO_SEGMENT.get(ticker.upper())


def get_segment_tickers(segment: str) -> list[str]:
    """Получить список тикеров в сегменте."""
    seg = MOEX_SEGMENTS.get(segment)
    return list(seg['tickers']) if seg else []


def get_all_segments() -> list[str]:
    """Список всех сегментов."""
    return list(MOEX_SEGMENTS.keys())


def get_segment_name_ru(segment: str) -> str:
    """Русское название сегмента."""
    seg = MOEX_SEGMENTS.get(segment)
    return seg['name_ru'] if seg else segment


def build_segment_index(
    segment: str,
    tf: str = 'D1',
    limit: int = 200,
    normalize_base: float = 1000.0,
) -> Optional[pd.DataFrame]:
    """
    Равновзвешенный сегментный индекс.

    Нормализует Close каждого тикера к первому значению (normalize_base),
    усредняет по всем тикерам сегмента.
    """
    tickers = get_segment_tickers(segment)
    if not tickers:
        return None

    price_dfs: dict[str, pd.DataFrame] = {}
    for ticker in tickers:
        try:
            df = load_dataframe(ticker, tf, limit=limit)
            if df is not None and len(df) > 10:
                for col in ['Open', 'High', 'Low', 'Close', 'Volume']:
                    df[col] = pd.to_numeric(df[col], errors='coerce')
                price_dfs[ticker] = df
        except Exception:
            continue

    if not price_dfs:
        return None

    # Все уникальные timestamps
    all_timestamps = sorted(set(
        ts for df in price_dfs.values()
        for ts in df['timestamp'].values
    ))
    if len(all_timestamps) < 5:
        return None

    normalized_prices: dict[str, np.ndarray] = {}
    volumes: dict[str, np.ndarray] = {}

    for ticker, df in price_dfs.items():
        close_series = pd.Series(df['Close'].values, index=df['timestamp'].values)
        vol_series = pd.Series(df['Volume'].values, index=df['timestamp'].values)

        first_close = close_series.iloc[0]
        if first_close == 0 or np.isnan(first_close):
            continue
        norm_close = close_series / first_close * normalize_base

        norm_interp = norm_close.reindex(all_timestamps, method='ffill')
        vol_interp = vol_series.reindex(all_timestamps, method='ffill')
        normalized_prices[ticker] = norm_interp.values
        volumes[ticker] = vol_interp.values

    if not normalized_prices:
        return None

    n_tickers = len(normalized_prices)
    index_close = np.nanmean(
        np.array([p for p in normalized_prices.values()]), axis=0,
    )
    index_volume = np.nansum(
        np.array([v for v in volumes.values()]), axis=0,
    )

    index_open = np.zeros(len(all_timestamps))
    index_high = np.zeros(len(all_timestamps))
    index_low = np.zeros(len(all_timestamps))
    index_open[0] = index_close[0]
    index_high[0] = index_close[0]
    index_low[0] = index_close[0]

    for i in range(1, len(all_timestamps)):
        ticker_returns = []
        for p in normalized_prices.values():
            if i < len(p) and i - 1 >= 0 and p[i - 1] > 0:
                ticker_returns.append(p[i] / p[i - 1])
        vol_factor = np.std(ticker_returns) * 0.5 if ticker_returns else 0.005
        index_open[i] = index_close[i - 1]
        index_high[i] = index_close[i] * (1 + abs(vol_factor))
        index_low[i] = index_close[i] * (1 - abs(vol_factor))

    result = pd.DataFrame({
        'timestamp': all_timestamps,
        'Open': index_open, 'High': index_high,
        'Low': index_low, 'Close': index_close, 'Volume': index_volume,
    })
    return result.sort_values('timestamp').reset_index(drop=True)


def analyze_segment(
    segment: str,
    tf: str = 'D1',
    limit: int = 200,
) -> dict[str, Any]:
    """
    Технический анализ сегмента: тренд, ADX, RSI, MACD.

    Возвращает:
        segment, name_ru, tickers, has_data,
        index_close, trend, adx, rsi, plus_di, minus_di,
        macd_hist, ha_trend, n_tickers, tickers_contributing,
        ema_50, ema_200
    """
    seg_info = MOEX_SEGMENTS.get(segment)
    if seg_info is None:
        return {'segment': segment, 'has_data': False, 'error': f'Сегмент {segment} не найден'}

    result: dict[str, Any] = {
        'segment': segment,
        'name_ru': seg_info['name_ru'],
        'tickers': seg_info['tickers'],
        'has_data': False,
        'index_close': None, 'trend': 'sideways',
        'adx': None, 'rsi': None,
        'plus_di': None, 'minus_di': None,
        'macd_hist': None, 'ha_trend': '—',
        'n_tickers': 0, 'tickers_contributing': [],
        'ema_50': None, 'ema_200': None,
    }

    index_df = build_segment_index(segment, tf, limit=limit)
    if index_df is None or len(index_df) < 20:
        return result

    result['has_data'] = True
    result['n_tickers'] = len(seg_info['tickers'])

    # Наши фичи содержат SMA/RMA варианты индикаторов
    index_df = engineer_features(index_df)
    last = index_df.iloc[-1]

    result['index_close'] = round(float(last['Close']), 2)

    # Тренд через SMA50/SMA200
    close = float(last['Close'])
    sma50 = float(last.get('close_to_sma_20', 0) * close + close)  # аппроксимация
    # Используем прямые SMA колонки если есть
    if 'SMA_50' in index_df.columns:
        ema50 = float(last['SMA_50']) if pd.notna(last['SMA_50']) else close
    else:
        ema50 = close * 0.99  # fallback

    if 'SMA_200' in index_df.columns:
        ema200 = float(last['SMA_200']) if pd.notna(last['SMA_200']) else close
    else:
        ema200 = close * 0.98  # fallback

    # ADX
    if 'adx' in index_df.columns:
        adx = float(last['adx']) if pd.notna(last['adx']) else 15
    else:
        adx = 15

    result['adx'] = round(adx, 1)

    # RSI
    if 'rsi' in index_df.columns:
        rsi = float(last['rsi']) if pd.notna(last['rsi']) else None
        result['rsi'] = round(rsi, 1) if rsi else None

    # MACD hist
    if 'macd_hist' in index_df.columns:
        macd_h = float(last['macd_hist']) if pd.notna(last['macd_hist']) else None
        result['macd_hist'] = round(macd_h, 3) if macd_h else None

    # Определение тренда
    score = 0
    if close > ema50 * 0.99:
        score += 2
    else:
        score -= 2
    if ema50 > ema200 * 0.99:
        score += 1
    if close > ema200 * 0.99:
        score += 1
    if adx > 20:
        # ADX сильный — проверяем направление через close_to_sma
        cts50 = float(last.get('close_to_sma_20', 0))
        if cts50 > 0.02:
            score += 2
        elif cts50 < -0.02:
            score -= 2

    # HH/HL
    if len(index_df) >= 10:
        recent_highs = index_df['High'].iloc[-5:].values
        recent_lows = index_df['Low'].iloc[-5:].values
        prev_highs = index_df['High'].iloc[-10:-5].values
        prev_lows = index_df['Low'].iloc[-10:-5].values
        if recent_highs.mean() > prev_highs.mean() and recent_lows.mean() > prev_lows.mean():
            score += 2
        elif recent_highs.mean() < prev_highs.mean() and recent_lows.mean() < prev_lows.mean():
            score -= 2

    if score >= 4:
        result['trend'] = 'up'
    elif score <= -4:
        result['trend'] = 'down'
    else:
        result['trend'] = 'sideways'

    result['tickers_contributing'] = seg_info['tickers']
    return result


def get_segment_context_for_ticker(
    ticker: str,
    tfs: Optional[list[str]] = None,
) -> dict[str, Any]:
    """
    Сегментный контекст для тикера по всем таймфреймам.

    Returns:
        segment, name_ru, tickers, analysis{TF: ...}, summary{trend_alignment, composite_trend, avg_rsi}
    """
    if tfs is None:
        tfs = ['W1', 'D1', 'H1']

    segment = get_segment_for_ticker(ticker)
    if segment is None:
        return {
            'segment': None, 'name_ru': None, 'tickers': [],
            'analysis': {},
            'summary': {'trend_alignment': 'unknown', 'composite_trend': 'sideways', 'avg_rsi': None},
        }

    seg_info = MOEX_SEGMENTS[segment]
    analysis_by_tf = {}
    for tf in tfs:
        try:
            analysis_by_tf[tf] = analyze_segment(segment, tf=tf, limit=200)
        except Exception as e:
            analysis_by_tf[tf] = {
                'segment': segment, 'name_ru': seg_info['name_ru'],
                'has_data': False, 'error': str(e),
            }

    trends = []
    rsis = []
    for tf_analysis in analysis_by_tf.values():
        if tf_analysis.get('has_data'):
            trends.append(tf_analysis['trend'])
            if tf_analysis.get('rsi') is not None:
                rsis.append(tf_analysis['rsi'])

    if trends:
        up_count = trends.count('up')
        down_count = trends.count('down')
        if up_count >= 2:
            composite = 'up'
            alignment = 'bullish'
        elif down_count >= 2:
            composite = 'down'
            alignment = 'bearish'
        else:
            composite = 'sideways'
            alignment = 'mixed'
        avg_rsi = np.mean(rsis) if rsis else None
    else:
        composite = 'sideways'
        alignment = 'unknown'
        avg_rsi = None

    return {
        'segment': segment,
        'name_ru': seg_info['name_ru'],
        'tickers': seg_info['tickers'],
        'analysis': analysis_by_tf,
        'summary': {
            'trend_alignment': alignment,
            'composite_trend': composite,
            'avg_rsi': round(float(avg_rsi), 1) if avg_rsi else None,
            'n_tfs_with_data': len(trends),
        },
    }
