"""
LLM service.
Wraps OpenRouter API calls and robust JSON parsing.
Kept separate from AnalysisService to avoid circular imports.
"""
import json
import requests
from typing import Dict, Any, Optional, List

from app.utils.logger import get_logger
from app.config import APIKeys
from app.utils.config_loader import load_addon_config

logger = get_logger(__name__)


class LLMService:
    """LLM provider wrapper."""

    def __init__(self):
        # Config may not be loaded yet during import time; we resolve lazily via properties.
        pass

    @property
    def api_key(self):
        return APIKeys.OPENROUTER_API_KEY

    @property
    def base_url(self):
        config = load_addon_config()
        # Keep compatible with old/new config keys.
        import os
        return config.get('openrouter', {}).get('base_url') or os.getenv('OPENROUTER_BASE_URL', "https://openrouter.ai/api/v1")

    def call_openrouter_api(self, messages: list, model: str = None, temperature: float = 0.7, use_fallback: bool = True) -> str:
        """Call OpenRouter API, with optional fallback models."""
        config = load_addon_config()
        openrouter_config = config.get('openrouter', {})
        
        default_model = openrouter_config.get('model', 'google/gemini-3-pro-preview')
        
        if model is None:
            model = default_model
            
        url = f"{self.base_url}/chat/completions"
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
            "HTTP-Referer": "https://quantdinger.com", 
            "X-Title": "QuantDinger Analysis" 
        }

        # Build model candidates (primary + optional fallbacks).
        models_to_try = [model]
        
        # Fallback models are currently hard-coded for local mode.
        fallback_models = ["openai/gpt-4o-mini"]
        
        if use_fallback and model == default_model:
            models_to_try.extend(fallback_models)

        last_error = None
        
        timeout = int(openrouter_config.get('timeout', 120))
        
        for current_model in models_to_try:
            try:
                data = {
                    "model": current_model,
                    "messages": messages,
                    "temperature": temperature,
                    "response_format": {"type": "json_object"}
                }
                # logger.debug(f"Trying model: {current_model}")

                response = requests.post(url, headers=headers, json=data, timeout=timeout)
                
                if response.status_code == 402:
                    logger.warning(f"OpenRouter returned 402 for model {current_model}; trying fallback model...")
                    last_error = f"402 Payment Required for model {current_model}"
                    continue
                
                response.raise_for_status()
                
                result = response.json()
                if "choices" in result and len(result["choices"]) > 0:
                    content = result["choices"][0]["message"]["content"]
                    if not content:
                        raise ValueError(f"Model {current_model} returned empty content")
                        
                    if current_model != model:
                        logger.info(f"Fallback model succeeded: {current_model}")
                    return content
                else:
                    logger.error(f"OpenRouter API returned unexpected structure ({current_model}): {json.dumps(result)}")
                    raise ValueError("OpenRouter API response is missing 'choices'")
                    
            except requests.exceptions.HTTPError as e:
                logger.error(f"OpenRouter API HTTP error ({current_model}): {e.response.text if e.response else str(e)}")
                last_error = str(e)
                if not use_fallback or current_model == models_to_try[-1]:
                    raise
            except requests.exceptions.RequestException as e:
                logger.error(f"OpenRouter API request error ({current_model}): {str(e)}")
                last_error = str(e)
                if not use_fallback or current_model == models_to_try[-1]:
                    raise
            except ValueError as e:
                logger.warning(f"Model {current_model} returned invalid data: {str(e)}")
                last_error = str(e)
                # If this is not the last candidate model, try the next one
                if current_model == models_to_try[-1]:
                    raise
        
        error_msg = f"All model calls failed. Last error: {last_error}"
        logger.error(error_msg)
        raise Exception(error_msg)

    def safe_call_llm(self, system_prompt: str, user_prompt: str, default_structure: Dict[str, Any], model: str = None) -> Dict[str, Any]:
        """Safe LLM call with robust JSON parsing and fallback structure."""
        response_text = ""
        try:
            response_text = self.call_openrouter_api([
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_prompt}
            ], model=model)
            
            # Strip markdown fences if present
            clean_text = response_text.strip()
            if clean_text.startswith("```"):
                first_newline = clean_text.find("\n")
                if first_newline != -1:
                    clean_text = clean_text[first_newline+1:]
                if clean_text.endswith("```"):
                    clean_text = clean_text[:-3]
            clean_text = clean_text.strip()
            
            # Parse JSON
            result = json.loads(clean_text)
            return result
        except json.JSONDecodeError:
            logger.error(f"JSON parse failed. Raw text: {response_text[:200] if response_text else 'N/A'}")
            
            # Try extracting JSON substring
            try:
                if response_text:
                    start = response_text.find('{')
                    end = response_text.rfind('}') + 1
                    if start >= 0 and end > start:
                        result = json.loads(response_text[start:end])
                        return result
            except:
                pass
            
            default_structure['report'] = f"Failed to parse analysis result JSON. Raw output (partial): {response_text[:500] if response_text else 'N/A'}"
            return default_structure
        except Exception as e:
            logger.error(f"LLM call failed: {str(e)}")
            default_structure['report'] = f"Analysis failed: {str(e)}"
            return default_structure
