"""
통합 LLM 클라이언트 — Gemini 2.5 Flash (Primary) + Ollama (Fallback)

설계 원칙:
  - OllamaManager.request_inference(prompt) 와 동일한 인터페이스 유지
    → process.py, ai_council.py 코드 변경 최소화
  - Gemini 실패(네트워크, Rate Limit) 시 자동으로 로컬 Ollama 폴백
  - 15 RPM 제한 준수를 위한 자동 스로틀링
  - VRAM 충돌 없음 (외부 API 호출이므로 LSTM 학습과 간섭 없음)

Rate Limit (Gemini 2.5 Flash 무료 티어):
  - 15 RPM, 1,500 RPD (봇 필요량 ~240/일 → 여유 6배)

추가 패키지 불필요:
  - requests (이미 설치됨) 기반 REST API 직접 호출
"""

import time
import requests
import json

from modules.config import Config


class GeminiLLMClient:
    """
    Gemini API 클라이언트

    사용법:
        client = GeminiLLMClient()
        result = client.request_inference(prompt)  # str | None
    """

    _GENERATE_URL = (
        "https://generativelanguage.googleapis.com/v1beta/models"
        "/{model}:generateContent?key={key}"
    )
    # 15 RPM → 최소 4초 간격 (여유 0.1초 추가)
    _MIN_INTERVAL = 4.1
    # 클래스 변수: 같은 프로세스 내 재생성 시에도 마지막 호출 시각 유지
    # (워커 OOM 재시작 후 싱글톤 교체 시에도 스로틀 유효)
    _class_last_call_ts: float = 0.0

    def __init__(self):
        self.api_key = Config.GEMINI_API_KEY
        self.model   = Config.GEMINI_MODEL
        self._ollama = None          # Ollama 폴백 (lazy init)
        self._use_gemini = bool(self.api_key)

        if self._use_gemini:
            print(f"✅ [LLMClient] Primary: Gemini {self.model}")
        else:
            print("⚠️  [LLMClient] GEMINI_API_KEY 미설정 → Ollama 전용 모드")

    # ── 내부 헬퍼 ────────────────────────────────────────────────────────────

    def _throttle(self):
        """15 RPM 제한 준수 — 최소 호출 간격 강제 대기 (클래스 공유 타임스탬프)"""
        elapsed = time.time() - GeminiLLMClient._class_last_call_ts
        if elapsed < self._MIN_INTERVAL:
            time.sleep(self._MIN_INTERVAL - elapsed)

    def _call_gemini(self, prompt: str) -> str | None:
        """
        Gemini REST API 단일 호출

        설정:
          - systemInstruction: JSON 전용 응답 강제
          - thinkingBudget=0: 내부 추론 비활성 (속도 1.5초 / 토큰 절약)
          - maxOutputTokens=512: 200은 thinking 소모로 잘리므로 여유 확보
        """
        self._throttle()

        url = self._GENERATE_URL.format(model=self.model, key=self.api_key)
        payload = {
            "system_instruction": {
                "parts": [{"text": (
                    "You are a Korean stock market analyst. "
                    "Respond with valid JSON only. "
                    "No markdown, no code blocks, no explanations."
                )}]
            },
            "contents": [{"parts": [{"text": prompt}]}],
            "generationConfig": {
                "maxOutputTokens": 512,   # 200→512 (thinking 비활성 후 실제 응답 공간 확보)
                "temperature": 0.1,       # 결정론적 출력
                "thinkingConfig": {"thinkingBudget": 0},  # 내부 추론 끔 (속도↑, 토큰↓)
            },
        }

        try:
            resp = requests.post(url, json=payload, timeout=30)
            GeminiLLMClient._class_last_call_ts = time.time()

            # Rate Limit 초과
            if resp.status_code == 429:
                print("[LLMClient] Gemini Rate Limit (429) → Ollama 폴백")
                return None

            resp.raise_for_status()
            data = resp.json()

            # thinking 파트 제외, 실제 텍스트 파트만 결합
            candidate = data.get("candidates", [{}])[0]
            parts = candidate.get("content", {}).get("parts", [])
            text = "".join(
                p.get("text", "") for p in parts
                if "text" in p and not p.get("thought")
            ).strip()

            return text if text else None

        except requests.exceptions.Timeout:
            print("[LLMClient] Gemini Timeout (30s) → Ollama 폴백")
            return None
        except Exception as e:
            print(f"[LLMClient] Gemini Error: {e} → Ollama 폴백")
            return None

    def _get_ollama(self):
        """Ollama 폴백 인스턴스 (lazy init — 필요할 때만 로드)"""
        if self._ollama is None:
            from modules.services.ollama import OllamaManager
            self._ollama = OllamaManager()
            # Ollama 실행 여부 사전 확인 (WinError 10061 조기 감지)
            try:
                requests.get(
                    f"{Config.OLLAMA_API_URL}/api/tags",
                    timeout=3,
                )
            except Exception:
                print(
                    f"❌ [LLMClient] Ollama 미실행 (localhost:11434 연결 거부) — "
                    f"`ollama serve` 명령으로 Ollama를 시작하세요."
                )
        return self._ollama

    # ── 공개 인터페이스 ───────────────────────────────────────────────────────

    def request_inference(self, prompt: str, context_data=None) -> str | None:
        """
        LLM 추론 요청 — OllamaManager.request_inference()와 동일한 시그니처

        순서:
          1) GEMINI_API_KEY 있음 → Gemini API 호출
          2) Gemini 실패(에러/타임아웃/Rate Limit) → Ollama 로컬 폴백
          3) GEMINI_API_KEY 없음 → 바로 Ollama 사용
        """
        if self._use_gemini:
            result = self._call_gemini(prompt)
            if result is not None:
                return result
            # Gemini 실패 → Ollama 폴백
            print("[LLMClient] Ollama 폴백 시도 중...")

        return self._get_ollama().request_inference(prompt, context_data)

    # ── OllamaManager 호환 메서드 (ai_council, evaluator 등에서 사용) ─────────

    def check_vram(self) -> float:
        """VRAM 사용량 반환 (Ollama 측 정보, Gemini 호출 시엔 무관)"""
        if self._ollama:
            return self._ollama.check_vram()
        return 0.0

    def get_gpu_status(self) -> dict:
        """GPU 상태 반환 (OllamaManager 호환)"""
        return self._get_ollama().get_gpu_status()

    def unload_model(self):
        """Ollama 모델 언로드 (LSTM 학습 전 호출용, Gemini는 무작동)"""
        if self._ollama:
            try:
                requests.post(
                    f"{Config.OLLAMA_API_URL}/api/generate",
                    json={"model": Config.OLLAMA_MODEL, "keep_alive": 0},
                    timeout=5,
                )
            except Exception:
                pass


# ── 워커 프로세스 전역 싱글톤 ─────────────────────────────────────────────────

_llm_client: GeminiLLMClient | None = None


def get_llm_client() -> GeminiLLMClient:
    """
    워커 프로세스 내 GeminiLLMClient 싱글톤 반환

    process.py에서 기존 get_ollama() 대신 이 함수를 사용:
        ollama = get_llm_client()
        result = ollama.request_inference(prompt)
    """
    global _llm_client
    if _llm_client is None:
        _llm_client = GeminiLLMClient()
    return _llm_client
