import subprocess
import sys
import re
import os
import tempfile
import time
from typing import List, Dict, Any, Set
from pathlib import Path
import shutil
import pkg_resources
import importlib
import logging

from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
from langchain.callbacks import get_openai_callback
from pydantic import BaseModel, Field
import pandas as pd
import numpy as np
from scipy.io import loadmat
import matplotlib.pyplot as plt

class FileInfo(BaseModel):
    path: str
    name: str
    extension: str
    size: int
    metadata: Dict[str, Any]
    temp_path: str = ""

class CodeExecutionResult(BaseModel):
    output: str = ""
    error: str | None = None
    execution_time: float = 0.0
    plot_path: str | None = None
    successful: bool = False
    attempts: int = 0

class FileManager:
    def __init__(self):
        self.uploaded_files: Dict[str, FileInfo] = {}
        self.temp_dir = tempfile.mkdtemp()

    def upload_file(self, file_path: str) -> FileInfo:
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileNotFoundError(f"File {file_path} not found")

        file_info = FileInfo(
            path=str(file_path),
            name=file_path.name,
            extension=file_path.suffix.lower(),
            size=file_path.stat().st_size,
            metadata=self._analyze_file(file_path)
        )
        
        dest_path = Path(self.temp_dir) / file_path.name
        shutil.copy2(file_path, dest_path)
        file_info.temp_path = str(dest_path)
        
        self.uploaded_files[file_path.name] = file_info
        return file_info

    def _analyze_file(self, file_path: Path) -> dict:
        ext = file_path.suffix.lower()
        metadata = {}

        try:
            if ext == '.csv':
                df = pd.read_csv(file_path)
                metadata = {
                    'rows': len(df),
                    'columns': list(df.columns),
                    'dtypes': {str(k): str(v) for k, v in df.dtypes.items()}
                }
            elif ext in ['.xlsx', '.xls']:
                df = pd.read_excel(file_path)
                metadata = {
                    'rows': len(df),
                    'columns': list(df.columns),
                    'dtypes': {str(k): str(v) for k, v in df.dtypes.items()}
                }
            elif ext == '.mat':
                mat_data = loadmat(file_path)
                metadata = {
                    'variables': list(mat_data.keys()),
                    'shapes': {k: str(v.shape) for k, v in mat_data.items() 
                             if isinstance(v, np.ndarray)}
                }
        except Exception as e:
            metadata['error'] = str(e)

        return metadata

class CodeInterpreter:
    # Standard library modules that don't need pip installation
    STANDARD_LIBRARY_MODULES = {
        'random', 'math', 'datetime', 'time', 'os', 'sys', 'json', 
        'csv', 'sqlite3', 'collections', 'itertools', 'functools',
        're', 'string', 'logging', 'io', 'pathlib', 'uuid', 'copy',
        'pickle', 'gzip', 'zipfile', 'tarfile', 'hashlib', 'hmac',
        'base64', 'typing', 'enum', 'statistics'
    }

    def __init__(self, api_key: str):
        os.environ["OPENAI_API_KEY"] = api_key
        self.llm = ChatOpenAI(
            temperature=0.7,
            model="gpt-3.5-turbo"
        )
        self.file_manager = FileManager()
        self.max_retries = 3
        self.installed_packages: Set[str] = set(pkg_resources.working_set.by_key.keys())
        
        # Configure logging
        logging.basicConfig(level=logging.INFO)
        self.logger = logging.getLogger("CodeInterpreter")

    def ensure_packages(self, code: str) -> bool:
        """Ensure all required packages are installed."""
        required_packages = self.extract_imports(code)
        
        # Filter out standard library modules
        external_packages = required_packages - self.STANDARD_LIBRARY_MODULES
        missing_packages = external_packages - self.installed_packages
        
        for package in missing_packages:
            if not self.install_package(package):
                return False
                
        return True

    def execute_code_with_retry(self, code: str) -> CodeExecutionResult:
        """Execute code with automatic retries and package installation."""
        result = CodeExecutionResult()
        
        for attempt in range(self.max_retries):
            result.attempts = attempt + 1
            
            # Ensure required packages are installed
            if not self.ensure_packages(code):
                result.error = "Failed to install required packages"
                continue
                
            # Prepare execution environment
            locals_dict = {
                'pd': pd,
                'np': np,
                'plt': plt,
                'loadmat': loadmat,
                'uploaded_files': self.file_manager.uploaded_files
            }
            
            # Add imported modules to locals
            for package in self.extract_imports(code):
                try:
                    if package in self.STANDARD_LIBRARY_MODULES:
                        # Import standard library modules directly
                        locals_dict[package] = importlib.import_module(package)
                    elif package in self.installed_packages:
                        # Import installed third-party packages
                        locals_dict[package] = importlib.import_module(package)
                except ImportError as e:
                    self.logger.warning(f"Failed to import {package}: {e}")
            
            try:
                start_time = time.time()
                
                # Execute the code
                exec(code, globals(), locals_dict)
                result.execution_time = time.time() - start_time
                
                # Handle matplotlib plots
                if plt.get_fignums():
                    plot_path = os.path.join(self.file_manager.temp_dir, f'plot_{int(time.time())}.png')
                    plt.savefig(plot_path)
                    plt.close()
                    result.plot_path = plot_path
                
                result.successful = True
                break
                
            except Exception as e:
                result.error = f"Attempt {result.attempts}: {str(e)}"
                self.logger.warning(f"Execution failed: {result.error}")
                time.sleep(1)  # Brief pause between retries
                
        if not result.successful:
            self.logger.error(f"Code execution failed after {result.attempts} attempts")
            
        return result
no should be able to tell what come with python what it need to install with pip