import os
import json
import time
import numpy as np
from modules.services.llm_client import get_llm_client
from modules.analysis.technical import TechnicalAnalyzer
from modules.analysis.deep_learning import ModelRegistry
from modules.analysis.market_regime import MarketRegimeDetector
from modules.analysis.ai_council import get_council
from modules.analysis.ensemble import get_ensemble
from modules.config import Config

# AI Council 마지막 호출 시각 캐시 (종목별, 과다 호출 방지)
_council_last_call: dict = {}


def get_predictor(ticker=None):
    """워커 프로세스 내에서 ModelRegistry로 종목별 PricePredictor 관리"""
    registry = ModelRegistry.get_instance()
    return registry.get_predictor(ticker or "default")


def get_ollama():
    """LLMClient 싱글톤 반환 (Gemini 우선, Ollama 폴백)"""
    return get_llm_client()


def calculate_position_size(total_capital, current_price, volatility, score, ai_confidence,
                            max_per_stock=3000000, ticker=None):
    """
    [v3.1] Modified Kelly Criterion 기반 포지션 사이징

    핵심 원칙:
    1. Kelly Fraction: f* = (p*b - q) / b  (과거 실전 승률 + 손익비 기반)
       - 데이터 부족 시 보수적 기본값 8% 사용
       - Half-Kelly 적용으로 변동성 과대추정 보완
    2. 변동성 조절: ATR 기반 변동성에 따라 Kelly 비중 추가 조절
    3. 확신도 조절: 앙상블 score에 따른 최종 배수
    4. AI 신뢰도 가산: LSTM confidence 기반 (상한 0.80 반영)
    5. 상한: min(종목당 최대, 자산의 20%, 실제 자산)

    Returns:
        int: 매수 수량 (0이면 매수 안 함)
    """
    if current_price <= 0 or total_capital <= 0:
        return 0

    # 1. Kelly Fraction 기반 기본 투자 비중
    ensemble = get_ensemble()
    kelly_f = ensemble.get_kelly_fraction(ticker=ticker, half_kelly=True)
    base_invest = total_capital * kelly_f

    # 2. 변동성 조절 계수 (ATR% 기반, 변동성 높을수록 축소)
    if volatility <= 1.0:
        vol_factor = 1.2
    elif volatility <= 2.0:
        vol_factor = 1.0
    elif volatility <= 3.0:
        vol_factor = 0.7
    elif volatility <= 5.0:
        vol_factor = 0.45
    else:
        vol_factor = 0.3

    # 3. 앙상블 확신도 조절 계수 (score 기반)
    if score >= 0.85:
        conf_factor = 2.0
    elif score >= 0.75:
        conf_factor = 1.5
    elif score >= 0.65:
        conf_factor = 1.0
    else:
        conf_factor = 0.5

    # 4. AI 신뢰도 가산 (LSTM confidence 상한 0.80 반영)
    ai_bonus = 1.0
    if ai_confidence >= 0.75:
        ai_bonus = 1.2
    elif ai_confidence >= 0.65:
        ai_bonus = 1.1

    # 5. 최종 투자금 계산
    invest_amount = base_invest * vol_factor * conf_factor * ai_bonus

    invest_amount = min(invest_amount, max_per_stock)        # 종목당 최대
    invest_amount = min(invest_amount, total_capital * 0.20)  # 자산 20% 상한
    invest_amount = min(invest_amount, total_capital)

    qty = int(invest_amount / current_price)
    kelly_pct = invest_amount / total_capital * 100 if total_capital > 0 else 0
    print(f"   [Kelly] f={kelly_f:.2%} invest={invest_amount:,.0f}won ({kelly_pct:.1f}%) qty={qty}")
    return max(0, qty)


def analyze_stock_process(ticker, ohlcv_data, news_items, investor_trend=None,
                          macro_status=None, holding_info=None, total_capital=None):
    """
    [v3.1] 종목 분석 + 매매 판단 (ProcessPoolExecutor에서 실행)

    [v3.1 개선사항]
    1. AdaptiveEnsemble 연동: 하드코딩 가중치 → 학습 기반 동적 가중치
    2. Kelly Criterion 기반 포지션 사이징 (calculate_position_size)
    3. 파일 mtime 동기화: 메인 프로세스의 record_trade 결과를 워커에 반영
    [v3.0 기능 유지]
    4. OHLCV 전체 수신 (실제 고가/저가/거래량 사용)
    5. 종목별 ModelRegistry (가중치 덮어쓰기 방지)
    6. 강화된 LLM 프롬프트
    """
    try:
        # [v3.1] 메인 프로세스가 갱신한 앙상블 가중치 파일 감지 → 재로드
        get_ensemble().reload_if_stale()
        # OHLCV 데이터 분리 (하위호환: list 형태도 허용)
        if isinstance(ohlcv_data, dict):
            prices = ohlcv_data.get('close', [])
            high_prices = ohlcv_data.get('high') or None
            low_prices = ohlcv_data.get('low') or None
            volume_history = ohlcv_data.get('volume') or None
            open_prices = ohlcv_data.get('open') or None
        else:
            # 하위 호환: 기존 close 리스트
            prices = ohlcv_data if isinstance(ohlcv_data, list) else []
            high_prices = None
            low_prices = None
            volume_history = None
            open_prices = None

        # volume이 모두 0이거나 비어있으면 None 처리
        if volume_history and all(v == 0 for v in volume_history):
            volume_history = None

        print(f"⚙️ [Bot Process] Analyzing {ticker} ({len(prices)} candles, "
              f"OHLCV={'yes' if high_prices else 'close-only'}, "
              f"Vol={'yes' if volume_history else 'no'})...")

        # ===== 1. 기술적 지표 계산 =====
        current_price = prices[-1] if prices else 0
        tech_score, rsi, volatility, vol_ratio, ma_info = TechnicalAnalyzer.get_technical_score(
            current_price, prices, volume_history=volume_history)

        # ===== 2. ATR 기반 동적 손절/익절 (실제 고가/저가 사용) =====
        sl_tp = TechnicalAnalyzer.calculate_dynamic_sl_tp(
            prices, high_prices=high_prices, low_prices=low_prices)

        # ===== 3. 볼린저밴드 위치 계산 =====
        bb_upper, bb_mid, bb_lower = TechnicalAnalyzer.calculate_bollinger_bands(prices)
        if bb_upper > bb_lower:
            bb_pos = (current_price - bb_lower) / (bb_upper - bb_lower)  # 0=하단, 1=상단
            if bb_pos <= 0.2:
                bb_zone = "하단(과매도)"
            elif bb_pos >= 0.8:
                bb_zone = "상단(과매수)"
            else:
                bb_zone = f"중간({bb_pos:.0%})"
        else:
            bb_pos = 0.5
            bb_zone = "중간"

        # ===== 4. LSTM 주가 예측 (ModelRegistry 사용) =====
        lstm_predictor = get_predictor(ticker)
        if lstm_predictor:
            lstm_predictor.training_status['current_ticker'] = ticker

        # LSTM에 전달할 OHLCV 딕셔너리 구성
        lstm_ohlcv = {
            'close': prices,
            'open': open_prices or prices,
            'high': high_prices or prices,
            'low': low_prices or prices,
            'volume': volume_history or []
        }
        pred_result = lstm_predictor.train_and_predict(lstm_ohlcv, ticker=ticker)

        lstm_score = 0.5
        ai_confidence = 0.5
        ai_loss = 1.0

        if pred_result:
            ai_confidence = pred_result.get('confidence', 0.5)
            ai_loss = pred_result.get('loss', 1.0)

            change_magnitude = min(abs(pred_result['change_rate']), 5.0) / 5.0

            if pred_result['trend'] == 'UP':
                lstm_score = 0.5 + (change_magnitude * ai_confidence * 0.4)
            else:
                lstm_score = 0.5 - (change_magnitude * ai_confidence * 0.4)

            lstm_score = max(0.0, min(1.0, lstm_score))

        # ===== 5. 수급 분석 (외인/기관) =====
        investor_score = 0.0
        frgn_net_buy = 0
        orgn_net_buy = 0
        consecutive_frgn_buy = 0
        consecutive_orgn_buy = 0

        if investor_trend:
            for day in investor_trend:
                frgn_net_buy += day['foreigner']
                orgn_net_buy += day['institutional']

            # 연속 매수일 수: 가장 최근부터 역순으로 연속된 양수 일수만 카운트
            for day in reversed(investor_trend):
                if day['foreigner'] > 0:
                    consecutive_frgn_buy += 1
                else:
                    break
            for day in reversed(investor_trend):
                if day['institutional'] > 0:
                    consecutive_orgn_buy += 1
                else:
                    break

            if frgn_net_buy > 0:
                investor_score += 0.03
            if consecutive_frgn_buy >= 3:
                investor_score += 0.04
            if consecutive_frgn_buy >= 5:
                investor_score += 0.03

            if orgn_net_buy > 0:
                investor_score += 0.02
            if consecutive_orgn_buy >= 3:
                investor_score += 0.03

            if frgn_net_buy > 0 and orgn_net_buy > 0:
                investor_score += 0.03
                print(f"   💰 [Investor] Both Foreign & Institutional Buying!")

        # ===== 6. AI 뉴스 분석 (강화된 프롬프트) =====
        if pred_result:
            pred_price = pred_result.get('predicted', 0)
            pred_change = pred_result.get('change_rate', 0)
        else:
            pred_price = current_price
            pred_change = 0.0

        news_summary = "; ".join(
            [n.get('title', '') for n in (news_items or [])[:3] if n.get('title')]
        ) or "뉴스 없음"

        # 거시경제 상태
        macro_state = macro_status.get('status', 'SAFE') if macro_status else 'SAFE'

        # 거래량 급증 여부
        vol_surge = "급증(x{:.1f})".format(vol_ratio) if vol_ratio >= 2.0 else "정상"

        # 보유종목 수익률
        holding_yield_str = ""
        if holding_info and holding_info.get('qty', 0) > 0:
            yld = holding_info.get('yield', 0.0)
            holding_yield_str = f" | 보유수익률={yld:+.1f}%"

        ollama = get_ollama()
        prompt = (
            f"Korean stock analyst. JSON only: {{\"sentiment_score\":0.0-1.0,\"reason\":\"1 sentence\"}}\n"
            f"Stock {ticker} ₩{current_price:,.0f}{holding_yield_str}\n"
            f"Market={macro_state} | "
            f"Tech={tech_score:.2f} RSI={rsi:.1f} MA={ma_info['trend']} ADX={ma_info.get('adx',20):.0f} "
            f"MTF={ma_info.get('mtf_alignment','N/A')}\n"
            f"BB={bb_zone} | AI={pred_change:+.2f}% conf={ai_confidence:.0%} | "
            f"Vol={volatility:.1f}% VolRatio={vol_surge}\n"
            f"Flow: Frgn={frgn_net_buy:+,}({consecutive_frgn_buy}d) "
            f"Inst={orgn_net_buy:+,}({consecutive_orgn_buy}d)\n"
            f"News: {news_summary}"
        )
        ai_resp = ollama.request_inference(prompt)
        sentiment_score = 0.5
        ai_reason = ""
        try:
            data = json.loads(ai_resp)
            sentiment_score = float(data.get("sentiment_score", 0.5))
            sentiment_score = max(0.0, min(1.0, sentiment_score))
            ai_reason = data.get("reason", "")
        except Exception:
            print(f"   ⚠️ AI response parse failed, using neutral (0.5)")

        # ===== 7. 통합 점수 (AdaptiveEnsemble v3.1) =====
        # 하드코딩 가중치 → 학습 기반 동적 가중치 (과거 매매 결과 반영)
        adx_val = ma_info.get('adx', 20)

        ensemble = get_ensemble()
        weights = ensemble.get_weights(
            ticker=ticker,
            adx=adx_val,
            macro_state=macro_state,
            ai_confidence=ai_confidence
        )
        print(f"   [Ensemble] tech={weights.tech:.2f} news={weights.sentiment:.2f} "
              f"lstm={weights.lstm:.2f} (adx={adx_val:.0f} conf={ai_confidence:.2f})")

        total_score = ensemble.compute_ensemble_score(
            tech_score=tech_score,
            sentiment_score=sentiment_score,
            lstm_score=lstm_score,
            investor_score=investor_score,
            weights=weights
        )

        # ===== 7.5. 시장 레짐 감지 (코스피 수준 기반) =====
        kospi_price = 0.0
        kospi_change_val = 0.0
        regime_analysis = None
        if macro_status:
            kospi_info = macro_status.get('indicators', {}).get('KOSPI', {})
            kospi_price = float(kospi_info.get('price', 0) or 0)
            kospi_change_val = float(kospi_info.get('change', 0) or 0)

        if Config.MARKET_REGIME_ENABLED and kospi_price > 0:
            regime_analysis = MarketRegimeDetector.detect(kospi_price, kospi_change_val)
            print(
                f"   📈 [Regime] {MarketRegimeDetector.get_regime_label(kospi_price)} "
                f"risk={regime_analysis.risk_level} "
                f"buy_adj={regime_analysis.buy_threshold_adj:+.2f} "
                f"pos=x{regime_analysis.position_size_adj:.2f}"
            )

        # ===== 8. 시장 상황별 동적 임계값 =====
        buy_threshold = 0.60
        sell_threshold = 0.30
        danger_force_sell = False  # DANGER 긴급 매도 플래그

        if macro_status:
            if macro_state == 'DANGER':
                buy_threshold = 999.0
                sell_threshold = 0.35   # 이전 0.45에서 하향 (더 적극적 손절)
                print(f"   🚨 [DANGER Market] Buy BLOCKED, Sell threshold lowered to 0.35")
                # 보유 중이고 손실이면 즉시 매도 플래그
                if holding_info and holding_info.get('qty', 0) > 0:
                    hy = holding_info.get('yield', 0.0)
                    if hy < -3.0:
                        danger_force_sell = True
                        print(f"   🚨 [DANGER + Loss {hy:.1f}%] Emergency Sell Triggered")
            elif macro_state == 'CAUTION':
                buy_threshold = 0.72
                sell_threshold = 0.38
                print(f"   ⚠️ [CAUTION Market] Buy threshold raised to 0.72")

        # 레짐 기반 임계값 추가 조정 (거시경제 판단 이후 적용)
        if regime_analysis and macro_state != 'DANGER':
            buy_threshold = round(
                max(0.55, buy_threshold + regime_analysis.buy_threshold_adj), 3
            )

        # ===== 9. 매매 결정 =====
        decision = "HOLD"
        decision_reason = ""

        # DANGER 긴급 매도 (손실 보유종목)
        if danger_force_sell:
            decision = "SELL"
            decision_reason = f"Emergency DANGER Market + Loss ({holding_info.get('yield', 0.0):.1f}%)"

        if holding_info:
            holding_yield = holding_info.get('yield', 0.0)
            holding_qty = holding_info.get('qty', 0)
            peak_price = holding_info.get('peak_price', current_price)

            if holding_qty > 0:
                if holding_yield <= sl_tp['stop_loss_pct']:
                    decision = "SELL"
                    decision_reason = f"Dynamic Stop Loss ({holding_yield:.1f}% <= {sl_tp['stop_loss_pct']:.1f}%)"

                elif holding_yield >= sl_tp['take_profit_pct']:
                    decision = "SELL"
                    decision_reason = f"Dynamic Take Profit ({holding_yield:.1f}% >= {sl_tp['take_profit_pct']:.1f}%)"

                elif peak_price > 0:
                    drop_from_peak = ((current_price - peak_price) / peak_price) * 100
                    if drop_from_peak <= -sl_tp['trailing_stop_pct'] and holding_yield > 2.0:
                        decision = "SELL"
                        decision_reason = (f"Trailing Stop ({drop_from_peak:.1f}% from peak, "
                                           f"threshold: -{sl_tp['trailing_stop_pct']:.1f}%)")

                if decision == "HOLD" and total_score <= sell_threshold:
                    decision = "SELL"
                    decision_reason = f"Analysis Signal (Score: {total_score:.2f} <= {sell_threshold:.2f})"

                if decision == "HOLD" and adx_val >= 30:
                    mtf_align = ma_info.get('mtf_alignment', '')
                    if mtf_align == 'STRONG_BEAR' and holding_yield < 0:
                        decision = "SELL"
                        decision_reason = f"Strong Bear Trend Reversal (MTF: {mtf_align})"

        # --- 매수 판단 ---
        if decision == "HOLD":
            strong_signal = False
            strong_reason = ""

            if tech_score >= 0.75 and lstm_score >= 0.6 and sentiment_score >= 0.6:
                strong_signal = True
                strong_reason = "Triple Confirmation (Tech+AI+News)"
            elif lstm_score >= 0.78 and ai_confidence >= 0.75 and adx_val >= 25:
                strong_signal = True
                strong_reason = f"High Confidence AI + Strong Trend (ADX={adx_val:.0f})"
            elif investor_score >= 0.10 and tech_score >= 0.60 and total_score >= 0.60:
                strong_signal = True
                strong_reason = "Institutional Buying + Good Fundamentals"
            elif ma_info.get('mtf_alignment') == 'STRONG_BULL' and tech_score >= 0.60:
                strong_signal = True
                strong_reason = f"Strong Multi-Timeframe Bullish + Tech {tech_score:.2f}"

            if strong_signal and total_score >= buy_threshold - 0.05:
                decision = "BUY"
                decision_reason = strong_reason
                print(f"   🎯 [{strong_reason}] → BUY!")
            elif total_score >= buy_threshold:
                decision = "BUY"
                decision_reason = f"Score {total_score:.2f} >= threshold {buy_threshold:.2f}"

        # ===== 10. 포지션 사이징 =====
        # total_capital: 호출 측에서 실제 잔고 전달 (없으면 보수적 기본값 5M)
        _capital = total_capital if (total_capital and total_capital > 0) else 5_000_000
        suggested_qty = 0
        if decision == "BUY":
            suggested_qty = calculate_position_size(
                total_capital=_capital,
                current_price=current_price,
                volatility=volatility,
                score=total_score,
                ai_confidence=ai_confidence,
                ticker=ticker
            )
            if suggested_qty == 0:
                decision = "HOLD"
                decision_reason = "Position size too small"

        # 레짐 기반 포지션 크기 조정 (이미 계산된 수량에 배수 적용)
        if regime_analysis and suggested_qty > 0:
            adjusted_qty = int(suggested_qty * regime_analysis.position_size_adj)
            if adjusted_qty != suggested_qty:
                print(f"   📐 [Regime] 포지션 조정: {suggested_qty} → {adjusted_qty}주 "
                      f"(x{regime_analysis.position_size_adj:.2f})")
                suggested_qty = max(0, adjusted_qty)
                if suggested_qty == 0:
                    decision = "HOLD"
                    decision_reason = "Regime position size adjustment → 0"

        print(f"   └─ Scores: Tech={tech_score:.2f} News={sentiment_score:.2f} "
              f"LSTM={lstm_score:.2f} Inv={investor_score:.2f} → "
              f"Total={total_score:.2f} [{decision}]"
              f"{f' ({decision_reason})' if decision_reason else ''}")

        # ===== 11. AI 전문가 회의 (선택적, Config.AI_COUNCIL_ENABLED) =====
        council_decision = None
        if Config.AI_COUNCIL_ENABLED:
            now = time.time()
            last_call = _council_last_call.get(ticker, 0)
            if now - last_call >= Config.AI_COUNCIL_MIN_INTERVAL:
                _council_last_call[ticker] = now
                council_data = {
                    "current_price": current_price,
                    "kospi_price": kospi_price,
                    "macro_state": macro_state,
                    "tech_score": tech_score,
                    "rsi": rsi,
                    "adx": adx_val,
                    "volatility": volatility,
                    "bb_zone": bb_zone,
                    "mtf_alignment": ma_info.get('mtf_alignment', 'N/A'),
                    "lstm_predicted": (
                        pred_result.get('predicted', current_price)
                        if pred_result else current_price
                    ),
                    "lstm_change_rate": (
                        pred_result.get('change_rate', 0) if pred_result else 0
                    ),
                    "ai_confidence": ai_confidence,
                    "lstm_score": lstm_score,
                    "sentiment_score": sentiment_score,
                    "investor_score": investor_score,
                    "frgn_net_buy": frgn_net_buy,
                    "consecutive_frgn_buy": consecutive_frgn_buy,
                    "is_holding": (
                        holding_info.get('qty', 0) > 0 if holding_info else False
                    ),
                    "holding_yield": (
                        holding_info.get('yield', 0.0) if holding_info else 0.0
                    ),
                    "total_score": total_score,
                }
                try:
                    council = get_council(get_ollama())
                    council_decision = council.convene(
                        ticker, council_data,
                        regime_analysis=regime_analysis,
                        fast_mode=Config.AI_COUNCIL_FAST_MODE,
                    )
                    # 모델 교체 권고 경고 출력
                    if council_decision.model_replacement_recommended:
                        print(
                            f"   ⚠️ [Council] 모델 교체 권고: "
                            f"{council_decision.recommended_model}"
                        )
                    # 회의 결정이 기존 결정과 다르고 신뢰도 높으면 우선 적용
                    if council_decision.confidence >= 0.75:
                        council_final = council_decision.final_decision.upper()
                        if council_final != decision:
                            print(
                                f"   🔄 [Council Override] {decision} → {council_final} "
                                f"(conf={council_decision.confidence:.2f})"
                            )
                            decision = council_final
                            decision_reason = (
                                f"AI Council ({council_decision.confidence:.0%}): "
                                f"{council_decision.majority_reasoning[:80]}"
                            )
                            # BUY로 전환된 경우 수량 재계산
                            if decision == "BUY" and suggested_qty == 0:
                                suggested_qty = calculate_position_size(
                                    total_capital=_capital,
                                    current_price=current_price,
                                    volatility=volatility,
                                    score=council_decision.confidence,
                                    ai_confidence=ai_confidence,
                                    ticker=ticker,
                                )
                except Exception as _ce:
                    print(f"   [Council] 회의 오류: {_ce}")

        return {
            "ticker": ticker,
            "score": total_score,
            "tech": tech_score,
            "sentiment": sentiment_score,
            "lstm_score": lstm_score,
            "investor_score": investor_score,
            "volatility": volatility,
            "volume_ratio": vol_ratio,
            "prediction": pred_result,
            "decision": decision,
            "decision_reason": decision_reason,
            "current_price": current_price,
            "ma_info": ma_info,
            "sl_tp": sl_tp,
            "suggested_qty": suggested_qty,
            "ai_confidence": ai_confidence,
            "ai_reason": ai_reason,
            "regime": {
                "kospi_level": kospi_price,
                "regime": regime_analysis.regime.value if regime_analysis else "unknown",
                "description": regime_analysis.description if regime_analysis else "",
                "risk_level": regime_analysis.risk_level if regime_analysis else "LOW",
                "model_recommendation": (
                    regime_analysis.model_recommendation if regime_analysis else ""
                ),
            } if regime_analysis else None,
            "council": {
                "final": council_decision.final_decision,
                "confidence": council_decision.confidence,
                "model_health": council_decision.model_health_score,
                "replace_recommended": council_decision.model_replacement_recommended,
                "recommended_model": council_decision.recommended_model,
                "summary": council_decision.council_summary,
            } if council_decision else None,
        }

    except Exception as e:
        print(f"❌ [Worker Error] Failed to analyze {ticker}: {e}")
        import traceback
        traceback.print_exc()
        return {
            "ticker": ticker,
            "score": 0.0,
            "decision": "HOLD",
            "decision_reason": f"Error: {str(e)}",
            "current_price": 0,
            "sl_tp": {'stop_loss_pct': -5.0, 'take_profit_pct': 8.0, 'trailing_stop_pct': 3.0},
            "suggested_qty": 0,
            "error": str(e)
        }
