"""
Модуль сегментного анализа MOEX.

Позволяет группировать тикеры по отраслевым сегментам,
строить сегментные индексы и анализировать их перед анализом
отдельных тикеров.

Использование:
    from src.analysis.segments import (
        MOEX_SEGMENTS, get_segment_for_ticker,
        build_segment_index, analyze_segment,
    )

    # Получить сегмент для тикера
    seg = get_segment_for_ticker('SBER')  # → 'banks'

    # Построить индекс сегмента
    index_df = build_segment_index('banks', 'D1')

    # Проанализировать сегмент
    seg_analysis = analyze_segment(index_df, 'banks', 'D1')
"""

from typing import Any, Dict, List, Optional, Tuple

import numpy as np
import pandas as pd

from src.db.connection import fetch_ohlcv_combined
from src.indicators.calculations import (
    calc_all_indicators,
    calculate_ema,
    calculate_sma,
)

# ── Сегменты 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]:
    """
    Определить сегмент для тикера.

    Args:
        ticker: тикер (например, 'SBER').

    Returns:
        Название сегмента (например, 'banks') или None, если сегмент не найден.
    """
    return _TICKER_TO_SEGMENT.get(ticker.upper())


def get_segment_tickers(segment: str) -> List[str]:
    """
    Получить список тикеров в сегменте.

    Args:
        segment: название сегмента (например, 'banks').

    Returns:
        Список тикеров.
    """
    seg = MOEX_SEGMENTS.get(segment)
    if seg is None:
        return []
    return list(seg['tickers'])


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


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

    Индекс строится как среднее нормализованных цен закрытия всех тикеров
    сегмента на каждом баре. Нормализация: цена каждого тикера делится на
    первое значение цены в выборке.

    Args:
        segment: название сегмента (например, 'banks').
        tf: таймфрейм ('H1', 'D1', 'W1').
        limit: количество свечей для каждого тикера.
        normalize_base: базовое значение индекса (по умолч. 1000).

    Returns:
        DataFrame с колонками [timestamp, Open, High, Low, Close, Volume]
        сегментного индекса, или None, если данных недостаточно.
    """
    tickers = get_segment_tickers(segment)
    if not tickers:
        return None

    # Загружаем данные для каждого тикера
    price_dfs: Dict[str, pd.DataFrame] = {}
    for ticker in tickers:
        try:
            df = fetch_ohlcv_combined(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():
        # Создаём Series с timestamp как индекс
        ts_index = pd.Series(df['timestamp'].values, index=df['timestamp'].values)
        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,
    )

    # Рассчитываем Open/High/Low индекса на основе изменений
    # Используем close-to-close returns для аппроксимации
    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)):
        ret = index_close[i] / index_close[i-1] if index_close[i-1] > 0 else 1.0
        # Аппроксимируем волатильность на основе изменений между тикерами
        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])
        if ticker_returns:
            vol_factor = np.std(ticker_returns) * 0.5  # половина стандартного отклонения
        else:
            vol_factor = 0.005  # fallback 0.5%

        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))

    # Формируем итоговый DataFrame
    result = pd.DataFrame({
        'timestamp': all_timestamps,
        'Open': index_open,
        'High': index_high,
        'Low': index_low,
        'Close': index_close,
        'Volume': index_volume,
    })

    # Сортировка по возрастанию timestamp
    result = result.sort_values('timestamp').reset_index(drop=True)

    return result


def analyze_segment(
    segment: str,
    tf: str = 'D1',
    limit: int = 200,
) -> Dict[str, Any]:
    """
    Полный технический анализ сегмента.

    Строит сегментный индекс, рассчитывает индикаторы,
    определяет тренд и ключевые сигналы.

    Args:
        segment: название сегмента (например, 'banks').
        tf: таймфрейм ('H1', 'D1', 'W1').
        limit: количество свечей.

    Returns:
        Словарь с результатами анализа:
            - segment: название сегмента
            - name_ru: русское название
            - tickers: список тикеров
            - has_data: есть ли данные
            - index_close: последнее значение индекса
            - trend: тренд ('up'/'down'/'sideways')
            - adx: значение ADX
            - rsi: значение RSI
            - plus_di, minus_di: DMI
            - macd_hist: MACD гистограмма
            - ha_trend: Heikin-Ashi тренд
            - n_tickers: количество тикеров с данными
            - tickers_contributing: список тикеров, вошедших в индекс
    """
    seg_info = MOEX_SEGMENTS.get(segment)
    if seg_info is None:
        return {
            'segment': segment,
            'name_ru': segment,
            'tickers': [],
            '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(
        [t for t in seg_info['tickers'] if t in result.get('tickers_contributing', [])]
    )

    # Рассчитываем индикаторы на индексе
    index_df = calc_all_indicators(index_df)
    last = index_df.iloc[-1]

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

    # Тренд (через determine_trend, но с локальной реализацией для автономности)
    if 'EMA_50' in index_df.columns and 'ADX_14' in index_df.columns:
        close = float(last['Close'])
        ema50 = float(last['EMA_50']) if pd.notna(last['EMA_50']) else close
        ema200_val = float(last.get('EMA_200', ema50)) if pd.notna(last.get('EMA_200', ema50)) else ema50
        adx = float(last['ADX_14']) if pd.notna(last['ADX_14']) else 15
        plus_di = float(last['Plus_DI']) if pd.notna(last['Plus_DI']) else 20
        minus_di = float(last['Minus_DI']) if pd.notna(last['Minus_DI']) else 20

        result['adx'] = round(adx, 1)
        result['plus_di'] = round(plus_di, 1)
        result['minus_di'] = round(minus_di, 1)
        result['ema_50'] = round(ema50, 2)
        result['ema_200'] = round(ema200_val, 2)

        score = 0
        if close > ema50 * 0.99:
            score += 2
        else:
            score -= 2
        if ema50 > ema200_val * 0.99:
            score += 1
        if close > ema200_val * 0.99:
            score += 1
        if adx > 20:
            if plus_di > minus_di * 1.1:
                score += 2
            elif minus_di > plus_di * 1.1:
                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'

    # RSI
    if 'RSI_14' in index_df.columns:
        rsi = float(last['RSI_14']) if pd.notna(last['RSI_14']) 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

    # Heikin-Ashi тренд
    try:
        ha = _calc_heikin_ashi_trend(index_df)
        result['ha_trend'] = ha
    except Exception:
        pass

    # Список тикеров, вошедших в индекс
    result['tickers_contributing'] = list(
        set(seg_info['tickers']) & set(
            t for t in seg_info['tickers']
        )
    )
    result['n_tickers'] = len(result['tickers_contributing'])

    return result


def _calc_heikin_ashi_trend(df: pd.DataFrame, period: int = 3) -> str:
    """Расчёт тренда Heikin-Ashi для сегментного индекса."""
    if df is None or len(df) < period:
        return '—'

    ha_close = (df['Open'] + df['High'] + df['Low'] + df['Close']) / 4.0
    ha_open = df['Open'].copy()

    for i in range(1, len(ha_open)):
        ha_open.iloc[i] = (ha_open.iloc[i-1] + ha_close.iloc[i-1]) / 2.0

    ha_bull = ha_close >= ha_open
    last_n = ha_bull.tail(period)
    if all(last_n):
        return 'up'
    elif not any(last_n):
        return 'down'
    return 'sideways'


def get_segment_context_for_ticker(
    ticker: str,
    tfs: List[str] = None,
) -> Dict[str, Any]:
    """
    Получить сегментный контекст для тикера по всем таймфреймам.

    Args:
        ticker: тикер (например, 'SBER').
        tfs: список таймфреймов (по умолч. ['W1', 'D1', 'H1']).

    Returns:
        Словарь с анализом сегмента по каждому ТФ:
            {
                'segment': 'banks',
                'name_ru': 'Банки',
                'analysis': {
                    'W1': { ... результаты analyze_segment ... },
                    'D1': { ... },
                    'H1': { ... },
                },
                'summary': {
                    'trend_alignment': 'bullish' / 'bearish' / 'mixed',
                    'strongest_tf': 'W1' / 'D1' / 'H1',
                    'composite_trend': 'up' / 'down' / 'sideways',
                }
            }
        Или {'segment': None, 'name_ru': None, 'analysis': {}, 'summary': {}}
        если тикер не привязан к сегменту.
    """
    if tfs is None:
        tfs = ['W1', 'D1', 'H1']

    segment = get_segment_for_ticker(ticker)
    if segment is None:
        return {
            'segment': None,
            'name_ru': None,
            'analysis': {},
            'summary': {
                'trend_alignment': 'unknown',
                'strongest_tf': None,
                'composite_trend': 'sideways',
                'note': 'Тикер не привязан к сегменту',
            },
        }

    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')
        total = len(trends)

        if up_count >= 2:
            composite = 'up'
            alignment = 'bullish'
        elif down_count >= 2:
            composite = 'down'
            alignment = 'bearish'
        else:
            composite = 'sideways'
            alignment = 'mixed'

        # RSI контекст
        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(avg_rsi, 1) if avg_rsi else None,
            'n_tfs_with_data': len(trends),
        },
    }
