"""
Risk assessment module - evaluates impact of changes using local LLM.

Integrates with Ollama for local LLM-based analysis of symbol changes.
No cloud calls - everything runs locally.
"""

from typing import Dict, List, Optional
import requests
import json
from pathlib import Path
import subprocess
import platform
import sys
import time


# Ollama configuration
OLLAMA_HOST = "http://localhost:11434"
OLLAMA_MODEL = "llama3"
OLLAMA_TIMEOUT = 30  # seconds


class OllamaError(Exception):
    """Raised when Ollama integration fails."""
    pass


def is_ollama_available() -> bool:
    """
    Check if Ollama is running and accessible.
    
    Returns:
        True if Ollama is available, False otherwise
    """
    try:
        response = requests.get(
            f"{OLLAMA_HOST}/api/tags",
            timeout=5
        )
        return response.status_code == 200
    except (requests.ConnectionError, requests.Timeout):
        return False
    except Exception:
        return False


def prompt_ollama_installation() -> bool:
    """
    Prompt user to install Ollama with OS-specific instructions.
    
    Returns:
        True if user wants to install, False if user declines
    """
    print("\n" + "=" * 80)
    print("AI summary not available.")
    print("Install local AI engine (Ollama) for smarter risk assessment?")
    print("=" * 80)
    print()
    
    while True:
        try:
            response = input("  [Y] Yes    [N] No  > ").strip().upper()
            
            if response == "Y":
                return True
            elif response == "N":
                return False
            else:
                print("  Please enter Y or N")
                continue
        except (KeyboardInterrupt, EOFError):
            print("\n  Installation cancelled.")
            return False


def get_install_commands() -> tuple[str, List[str]]:
    """
    Get OS-specific Ollama installation commands.
    
    Returns:
        Tuple of (OS name, list of command strings to run)
    """
    system = platform.system()
    
    if system == "Darwin":  # macOS
        return (
            "macOS",
            [
                "brew install ollama",
                "ollama pull llama3",
                "ollama serve"
            ]
        )
    elif system == "Linux":
        return (
            "Linux",
            [
                "curl -fsSL https://ollama.com/install.sh | sh",
                "ollama pull llama3",
                "ollama serve"
            ]
        )
    elif system == "Windows":
        return (
            "Windows",
            [
                "Download from https://ollama.ai/download/windows",
                "Run installer",
                "ollama pull llama3",
                "ollama serve"
            ]
        )
    else:
        return (system, [])


def install_ollama() -> bool:
    """
    Guide user through Ollama installation and start the service.
    
    Returns:
        True if installation and startup successful, False otherwise
    """
    os_name, commands = get_install_commands()
    
    print("\n" + "=" * 80)
    print(f"Installing Ollama on {os_name}...")
    print("=" * 80)
    print()
    
    if not commands:
        print(f"❌ Unsupported operating system: {os_name}")
        print("Please visit https://ollama.ai for manual installation.")
        return False
    
    # For Windows, provide download link
    if os_name == "Windows":
        print("Please follow these steps:")
        for i, cmd in enumerate(commands, 1):
            print(f"  {i}. {cmd}")
        print("\nAfter installation, restart the assessment.")
        return False
    
    try:
        # Run installation commands except the last one (ollama serve)
        for cmd in commands[:-1]:
            print(f"📍 Running: {cmd}")
            print()
            
            try:
                subprocess.run(
                    cmd,
                    shell=True,
                    check=True,
                    timeout=300,  # 5 minute timeout per command
                    text=True
                )
                print()
            except subprocess.TimeoutExpired:
                print(f"❌ Command timed out: {cmd}")
                print("Please try manual installation from https://ollama.ai")
                return False
            except subprocess.CalledProcessError as e:
                print(f"❌ Command failed: {cmd}")
                print(f"Error: {str(e)}")
                print("Please try manual installation from https://ollama.ai")
                return False
            except Exception as e:
                print(f"⚠️  Error during installation: {str(e)}")
                print("Please try manual installation from https://ollama.ai")
                return False
        
        # Start Ollama server
        print(f"📍 Starting Ollama server: {commands[-1]}")
        print()
        print("⏳ Starting Ollama... (this may take a moment)")
        
        try:
            # Start ollama serve in background, wait a bit for it to start
            process = subprocess.Popen(
                commands[-1],
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE
            )
            
            # Wait for Ollama to be ready (up to 30 seconds)
            print("   Waiting for Ollama to be ready...", end="", flush=True)
            for i in range(30):
                time.sleep(1)
                if is_ollama_available():
                    print(" ✅ Ready!")
                    print()
                    print("=" * 80)
                    print("✅ Ollama installed and started successfully!")
                    print("=" * 80)
                    return True
                if i % 3 == 0:
                    print(".", end="", flush=True)
            
            print(" ⏱️  Timeout")
            print()
            print("⚠️  Ollama may still be starting. Please try the assessment again.")
            return False
            
        except Exception as e:
            print(f"❌ Failed to start Ollama: {str(e)}")
            return False
            
    except Exception as e:
        print(f"❌ Unexpected error during installation: {str(e)}")
        return False


def prompt_and_install_ollama_if_needed() -> bool:
    """
    Check if Ollama is available, and if not, prompt user for installation.
    
    Returns:
        True if Ollama is now available (or was already available)
        False if user declined installation or installation failed
    """
    if is_ollama_available():
        return True
    
    # Ollama not available - prompt user
    if not prompt_ollama_installation():
        return False
    
    # User wants to install
    if not install_ollama():
        return False
    
    # Verify installation was successful
    if is_ollama_available():
        return True
    
    print("\n⚠️  Ollama installation completed, but service is not responding.")
    print("Please try running: ollama serve")
    return False


def build_risk_prompt(symbol_name: str, usage_locations: List[str], change_type: Optional[str] = None) -> str:
    """
    Build a prompt for risk assessment.
    
    Args:
        symbol_name: Name of the changed symbol
        usage_locations: List of locations where symbol is used
        change_type: Type of change (e.g., "function signature changed")
        
    Returns:
        Formatted prompt string
    """
    usage_text = "\n".join(f"  - {loc}" for loc in usage_locations) if usage_locations else "  - (No usages found in diff)"
    
    change_context = ""
    if change_type:
        change_context = f"\nChange Type: {change_type}\n"
    
    prompt = f"""You are a code impact assessment expert. Analyze the following change:

Changed Symbol: {symbol_name}
{change_context}
Used in:
{usage_text}

Task: Based on the symbol name, change type, and usage across modules, classify the IMPACT RISK as one of:
- LOW: Isolated change, well-tested, minimal side effects
- MEDIUM: Used in multiple modules, requires testing, moderate risk
- HIGH: Critical dependency, used in core paths, payment/auth related, high risk

Provide:
1. Risk classification (LOW/MEDIUM/HIGH)
2. Brief reason (1-2 sentences)
3. Recommended validation steps (2-3 bullet points)

Be concise and actionable."""

    return prompt


def assess_risk(
    symbol_name: str,
    usage_locations: Optional[List[str]] = None,
    change_type: Optional[str] = None,
    fallback_to_heuristic: bool = True
) -> str:
    """
    Assess the risk of a symbol change using local Ollama LLM.
    
    Attempts to use Ollama for intelligent analysis. Falls back to heuristic
    scoring if Ollama is unavailable.
    
    Args:
        symbol_name: Name of the changed symbol
        usage_locations: List of places where symbol is used
        change_type: Type of change (e.g., "function added", "variable modified")
        fallback_to_heuristic: If True, use heuristic if Ollama unavailable
        
    Returns:
        Risk assessment text
        
    Raises:
        OllamaError: If Ollama fails and fallback_to_heuristic is False
    """
    if usage_locations is None:
        usage_locations = []
    
    # Try Ollama first
    if is_ollama_available():
        try:
            return _assess_with_ollama(symbol_name, usage_locations, change_type)
        except OllamaError as e:
            if not fallback_to_heuristic:
                raise
            print(f"⚠️  Ollama analysis failed: {str(e)}")
            print("   Falling back to heuristic assessment...\n")
    else:
        if not fallback_to_heuristic:
            raise OllamaError(
                f"Ollama not available at {OLLAMA_HOST}. "
                f"Start Ollama with: ollama serve"
            )
    
    # Fallback to heuristic
    return _assess_with_heuristic(symbol_name, usage_locations, change_type)


def _assess_with_ollama(symbol_name: str, usage_locations: List[str], change_type: Optional[str] = None) -> str:
    """
    Use Ollama to assess risk.
    
    Args:
        symbol_name: Name of changed symbol
        usage_locations: Places where symbol is used
        change_type: Type of change made
        
    Returns:
        Risk assessment from LLM
        
    Raises:
        OllamaError: If request fails
    """
    try:
        prompt = build_risk_prompt(symbol_name, usage_locations, change_type)
        
        payload = {
            "model": OLLAMA_MODEL,
            "prompt": prompt,
            "stream": False
        }
        
        response = requests.post(
            f"{OLLAMA_HOST}/api/generate",
            json=payload,
            timeout=OLLAMA_TIMEOUT
        )
        
        if response.status_code != 200:
            raise OllamaError(
                f"Ollama returned status {response.status_code}: {response.text}"
            )
        
        data = response.json()
        return data.get("response", "").strip()
        
    except requests.Timeout:
        raise OllamaError(
            f"Ollama request timed out (>{OLLAMA_TIMEOUT}s). "
            "Model may be slow or system overloaded."
        )
    except requests.ConnectionError:
        raise OllamaError(
            f"Cannot connect to Ollama at {OLLAMA_HOST}. "
            "Ensure Ollama is running: ollama serve"
        )
    except json.JSONDecodeError:
        raise OllamaError("Ollama returned invalid JSON response")
    except Exception as e:
        raise OllamaError(f"Unexpected error: {str(e)}")


def _assess_with_heuristic(symbol_name: str, usage_locations: List[str], change_type: Optional[str] = None) -> str:
    """
    Heuristic-based risk assessment (fallback when Ollama unavailable).
    
    Uses simple rules based on symbol name patterns and usage count.
    Takes into account the type of change when assessing risk.
    
    Args:
        symbol_name: Name of changed symbol
        usage_locations: Places where symbol is used
        change_type: Type of change made
        
    Returns:
        Risk assessment based on heuristics
    """
    usage_count = len(usage_locations)
    
    # Risk factors
    critical_keywords = {
        "payment", "auth", "security", "encrypt", "delete", "remove",
        "transaction", "checkout", "process", "admin", "token", "credential"
    }
    
    # Change type affects risk scoring
    high_risk_change_types = {
        "function removed",
        "class removed",
        "function signature changed"
    }
    
    is_critical = any(
        keyword in symbol_name.lower() for keyword in critical_keywords
    )
    
    is_high_risk_change = change_type in high_risk_change_types if change_type else False
    
    test_count = sum(
        1 for loc in usage_locations if "test" in loc.lower()
    )
    
    # Heuristic scoring
    if is_critical or is_high_risk_change or usage_count > 5:
        risk_level = "HIGH"
        reason = f"Symbol is critical, change is {'structural' if is_high_risk_change else 'high-impact'}, or used in many modules"
    elif usage_count > 2 or (usage_count > 0 and test_count == 0):
        risk_level = "MEDIUM"
        reason = "Multiple usages detected; test coverage unclear"
    else:
        risk_level = "LOW"
        reason = "Limited usage or well-tested"
    
    # Build response
    validation_steps = []
    
    # Add change-type specific steps
    if change_type:
        if "removed" in change_type:
            validation_steps.append(f"Verify all {usage_count} dependent modules handle removal")
        elif "signature" in change_type:
            validation_steps.append("Execute comprehensive function signature tests")
        elif "added" in change_type:
            validation_steps.append(f"Test new {change_type.split()[0]} in isolation")
    
    if is_critical:
        validation_steps.append("Execute security/payment integration tests")
    if usage_count > 0:
        validation_steps.append(f"Regression test across {usage_count} dependent modules")
    if test_count == 0 and usage_count > 0:
        validation_steps.append("Add unit tests if missing")
    if not validation_steps:
        validation_steps.append("Standard unit test execution")
    
    response = f"""Risk Classification: {risk_level}

Reason: {reason}

Validation Steps:
{chr(10).join(f'• {step}' for step in validation_steps[:3])}

Note: This assessment is heuristic-based. For AI-powered analysis, 
ensure Ollama is running locally."""
    
    return response


def assess_risk_for_symbols(symbols_usage: Dict[str, List[str]]) -> Dict[str, str]:
    """
    Assess risk for multiple symbols.
    
    Args:
        symbols_usage: Dict mapping symbol names to usage locations
        
    Returns:
        Dict mapping symbol names to risk assessments
    """
    results = {}
    
    for symbol_name, usage_locations in symbols_usage.items():
        try:
            results[symbol_name] = assess_risk(symbol_name, usage_locations)
        except OllamaError:
            # Fallback already attempted in assess_risk
            results[symbol_name] = f"Failed to assess: {symbol_name}"
    
    return results
