import sys
import os
import asyncio
import json
import subprocess
import ast
import re
import tempfile
import time
from datetime import datetime
from dotenv import load_dotenv
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QPushButton, QFileDialog,
    QTextEdit, QProgressBar, QMessageBox, QLabel, QTabWidget, QHBoxLayout, QLineEdit,
    QListWidget, QInputDialog
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
import aiohttp
from groq import AsyncGroq
from bs4 import BeautifulSoup
import toml
from aiolimiter import AsyncLimiter
import torch
from transformers import GPTNeoForCausalLM, GPT2Tokenizer
import nltk
import spacy

# Load environment variables
load_dotenv()

# Configuration
GROQ_API_KEY = os.getenv('GROQ_API_KEY')
MODEL_PATH = os.getenv('MODEL_PATH')
KNOWLEDGE_PATH = os.getenv('KNOWLEDGE_PATH', 'solana_knowledge_base.json')
MEMORY_PATH = os.getenv('MEMORY_PATH', 'memory.json')
ANALYZED_SCRIPTS_PATH = 'analyzed_scripts.json'

# Constants
MAX_MEMORY_ENTRIES = 200
MAX_ANALYZED_SCRIPTS = 200
MAX_GROQ_TOKENS = 2000  # Increased token limit for Groq queries

# Set device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")

# Load model with mixed precision
tokenizer = GPT2Tokenizer.from_pretrained(MODEL_PATH)
model = GPTNeoForCausalLM.from_pretrained(MODEL_PATH).to(device)
model.eval()
print("Model loaded successfully.")

# Load NLTK and SpaCy
nltk.download('punkt', quiet=True)
nltk.download('stopwords', quiet=True)
nltk.download('wordnet', quiet=True)
nlp = spacy.load('en_core_web_sm')

# Utility functions for JSON file operations
def load_json_file(file_path, default):
    if os.path.exists(file_path):
        with open(file_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    return default

def save_json_file(data, file_path):
    with open(file_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2)

# Load memory and knowledge base
memory = load_json_file(MEMORY_PATH, [])
analyzed_scripts = load_json_file(ANALYZED_SCRIPTS_PATH, [])
knowledge_base = load_json_file(KNOWLEDGE_PATH, {})

# Helper function for chunking text into smaller parts
def chunk_text(text, max_token_length):
    tokens = tokenizer.encode(text)
    return [tokens[i:i + max_token_length] for i in range(0, len(tokens), max_token_length)]

# Function to analyze a file and return results
def analyze_file(file_path):
    with open(file_path, 'r', encoding='utf-8') as file:
        content = file.read()

    analysis = {
        'file_name': os.path.basename(file_path),
        'content_length': len(content),
        'imports': [],
        'functions': [],
        'structs': [],
    }
    
    if file_path.endswith('.py'):
        try:
            tree = ast.parse(content)
            for node in ast.walk(tree):
                if isinstance(node, ast.Import):
                    analysis['imports'].extend(n.name for n in node.names)
                elif isinstance(node, ast.ImportFrom):
                    analysis['imports'].extend(f"{node.module}.{n.name}" for n in node.names)
                elif isinstance(node, ast.FunctionDef):
                    analysis['functions'].append(node.name)
                elif isinstance(node, ast.ClassDef):
                    analysis['structs'].append(node.name)
        except SyntaxError as e:
            print(f"Syntax error in {file_path}: {e}")
    elif file_path.endswith('.rs'):
        for line in content.split('\n'):
            if line.strip().startswith('use '):
                analysis['imports'].append(line.strip())
            elif match := re.match(r'fn\s+(\w+)', line.strip()):
                analysis['functions'].append(match.group(1))
            elif match := re.match(r'struct\s+(\w+)', line.strip()):
                analysis['structs'].append(match.group(1))

    return analysis

# Worker class for background tasks
class Worker(QThread):
    progress = pyqtSignal(int)
    log = pyqtSignal(str)
    finished = pyqtSignal()

    def __init__(self, folder):
        super().__init__()
        self.folder = folder

    def run(self):
        files = [f for f in os.listdir(self.folder) if f.endswith(('.py', '.rs'))]
        for i, file in enumerate(files):
            analysis = analyze_file(os.path.join(self.folder, file))
            self.log.emit(f"Analyzed {analysis['file_name']}: {analysis['content_length']} chars")
            if not any(a['file_name'] == analysis['file_name'] for a in analyzed_scripts):
                analyzed_scripts.append(analysis)
                if len(analyzed_scripts) > MAX_ANALYZED_SCRIPTS:
                    analyzed_scripts.pop(0)
            self.progress.emit(int((i + 1) / len(files) * 100))
        save_json_file(analyzed_scripts, ANALYZED_SCRIPTS_PATH)
        self.finished.emit()

# Functions to manage memory
def append_to_memory(entry):
    memory.append(entry)
    if len(memory) > MAX_MEMORY_ENTRIES:
        memory.pop(0)
    save_json_file(memory, MEMORY_PATH)

# Improved Local Model Inference with Chunking
async def local_model_inference(prompts, max_new_tokens=200):
    """
    Perform batched inference on a list of prompts using the local model.
    """
    responses = []
    for prompt in prompts:
        if isinstance(prompt, str):  # Ensure prompt is a string
            inputs = tokenizer(prompt, return_tensors="pt", truncation=True, padding=True).to(device)
            with torch.no_grad():
                with torch.cuda.amp.autocast():
                    try:
                        output = model.generate(**inputs, max_new_tokens=max_new_tokens, num_return_sequences=1)
                        responses.append(tokenizer.decode(output[0], skip_special_tokens=True))
                    except RuntimeError as e:
                        print(f"CUDA error occurred: {e}")
                        responses.append("Error during inference.")
        else:
            responses.append("Invalid input type. Must be a string.")
    return responses

# Web crawling with Groq API
async def groq_web_crawl(context, urls):
    async with aiohttp.ClientSession() as session:
        crawled_text = await efficient_web_crawl(urls)
        messages = [
            {"role": "system", "content": "Retrieve relevant Solana-related snippets."},
            {"role": "user", "content": f"{context}\n\nCrawled Content: {crawled_text}"}
        ]
        async with session.post("https://api.groq.com/v1/chat/completions",
                                json={"messages": messages, "model": 'mixtral-8x7b-32768', "max_tokens": MAX_GROQ_TOKENS},
                                headers={"Authorization": f"Bearer {GROQ_API_KEY}"}) as response:
            response_json = await response.json()
    return response_json["choices"][0]["message"]["content"].strip()

# Efficient web crawling implementation
async def efficient_web_crawl(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
    return " ".join(results)

async def fetch_url(session, url):
    async with session.get(url) as response:
        if response.status == 200:
            html = await response.text()
            soup = BeautifulSoup(html, 'html.parser')
            return soup.get_text()
        else:
            print(f"Failed to fetch URL: {url} with status code: {response.status}")
            return ""

# Code testing function with parallel execution
async def test_code(code, language):
    with tempfile.NamedTemporaryFile(mode='w', suffix=f'.{language}', delete=False) as temp_file:
        temp_file.write(code)
        temp_file_path = temp_file.name

    if language in ('py', 'python'):
        result = await run_python(temp_file_path)
    elif language in ('rs', 'rust'):
        result = await run_rust(temp_file_path)
    else:
        result = "Unsupported language"

    os.unlink(temp_file_path)
    return result

async def run_python(file_path):
    try:
        result = subprocess.run(['python', file_path], capture_output=True, text=True, timeout=5)
        return result.stdout if result.returncode == 0 else f"Error: {result.stderr}"
    except subprocess.TimeoutExpired:
        return "Error: Execution timed out"

async def run_rust(file_path):
    try:
        compile_result = subprocess.run(
            ['rustc', file_path, '-o', 'temp_rust_executable'], 
            capture_output=True, text=True, timeout=10
        )
        if compile_result.returncode != 0:
            return f"Compilation Error: {compile_result.stderr}"
        
        run_result = subprocess.run(
            ['./temp_rust_executable'], capture_output=True, text=True, timeout=5
        )
        os.unlink('temp_rust_executable')
        return run_result.stdout if run_result.returncode == 0 else f"Runtime Error: {run_result.stderr}"
    except subprocess.TimeoutExpired:
        return "Error: Execution timed out"

# Process chat messages
async def process_chat_message(message, urls):
    context = f"""
    Memory: {json.dumps(memory[-2:])}
    Analyzed Scripts: {json.dumps(analyzed_scripts[-2:])}
    Knowledge Base: {json.dumps(list(knowledge_base.items())[:2])}
    User Message: {message}
    """
    
    # Split context into chunks to avoid exceeding token limits
    prompt_chunks = chunk_text(context, 2048)  # chunking context into max allowed tokens
    local_responses = await local_model_inference(prompt_chunks)

    # First response (local model)
    local_response = local_responses[0] if local_responses else "No response generated."

    if "need more information" in local_response.lower():
        groq_response = await groq_web_crawl(context, urls)
        knowledge_base[message] = groq_response
        save_json_file(knowledge_base, KNOWLEDGE_PATH)
        final_response = (await local_model_inference(
            [context + "\nAdditional Info: " + groq_response[:500]]
        ))[0]  # Get the first response from the list
    else:
        final_response = local_response
    
    # Extract and test code blocks
    code_blocks = re.findall(r'```(\w+)\n(.*?)```', final_response, re.DOTALL)
    test_results = []
    for language, code in code_blocks:
        if language in ['python', 'py', 'rust', 'rs']:
            test_result = await test_code(code, language[:2])
            test_results.append(f"\n\nTest Result for {language} code:\n{test_result}")

    return final_response + "".join(test_results)

# PyQt6 GUI Application
class App(QWidget):
    def __init__(self):
        super().__init__()
        self.title = 'Solana Sniper Bot Analyzer and Enhancer'
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(100, 100, 900, 700)
        self.setAcceptDrops(True)

        layout = QVBoxLayout()
        self.tabs = QTabWidget()
        layout.addWidget(self.tabs)

        self.tab1 = QWidget()
        self.tabs.addTab(self.tab1, "Code Analyzer")
        self.tab1UI()

        self.tab2 = QWidget()
        self.tabs.addTab(self.tab2, "AI Chat")
        self.tab2UI()

        self.tab3 = QWidget()
        self.tabs.addTab(self.tab3, "URL Crawler")
        self.tab3UI()

        self.setLayout(layout)

    def tab1UI(self):
        layout = QVBoxLayout()

        self.title_label = QLabel("Solana Sniper Bot Code Analyzer and Enhancer")
        self.title_label.setStyleSheet("font-size: 18px; font-weight: bold;")
        self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.title_label)

        self.select_script_directory_btn = QPushButton('Select Directory for Scripts')
        self.select_script_directory_btn.clicked.connect(self.select_and_read_scripts)
        layout.addWidget(self.select_script_directory_btn)

        self.selected_folder_label = QLabel('No folder selected.')
        layout.addWidget(self.selected_folder_label)

        self.start_btn = QPushButton('Start Analysis')
        self.start_btn.clicked.connect(self.start_analysis)
        self.start_btn.setEnabled(False)
        layout.addWidget(self.start_btn)

        self.progress_bar = QProgressBar()
        layout.addWidget(self.progress_bar)

        self.log_text = QTextEdit()
        self.log_text.setReadOnly(True)
        layout.addWidget(self.log_text)

        self.tab1.setLayout(layout)

    def tab2UI(self):
        layout = QVBoxLayout()

        self.chat_display = QTextEdit()
        self.chat_display.setReadOnly(True)
        layout.addWidget(self.chat_display)

        h_layout = QHBoxLayout()
        self.chat_input = QLineEdit()
        self.chat_input.setPlaceholderText("Type your message here...")
        self.chat_input.returnPressed.connect(self.check_ctrl_enter)
        self.send_btn = QPushButton("Send")
        self.send_btn.clicked.connect(self.send_chat)
        h_layout.addWidget(self.chat_input)
        h_layout.addWidget(self.send_btn)

        layout.addLayout(h_layout)
        self.tab2.setLayout(layout)

    def tab3UI(self):
        layout = QVBoxLayout()

        self.url_list = QListWidget()
        layout.addWidget(self.url_list)

        add_url_btn = QPushButton("Add URL")
        add_url_btn.clicked.connect(self.add_url)
        layout.addWidget(add_url_btn)

        remove_url_btn = QPushButton("Remove URL")
        remove_url_btn.clicked.connect(self.remove_url)
        layout.addWidget(remove_url_btn)

        self.tab3.setLayout(layout)

    def add_url(self):
        url, ok = QInputDialog.getText(self, "Add URL", "Enter URL:")
        if ok and url:
            self.url_list.addItem(url)

    def remove_url(self):
        current_item = self.url_list.currentItem()
        if current_item:
            self.url_list.takeItem(self.url_list.row(current_item))

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        files = [u.toLocalFile() for u in event.mimeData().urls()]
        for file_path in files:
            self.analyze_dropped_file(file_path)

    def analyze_dropped_file(self, file_path):
        analysis = analyze_file(file_path)
        self.log_text.append(f"Analyzed dropped file: {analysis['file_name']}")
        if not any(a['file_name'] == analysis['file_name'] for a in analyzed_scripts):
            analyzed_scripts.append(analysis)
            if len(analyzed_scripts) > MAX_ANALYZED_SCRIPTS:
                analyzed_scripts.pop(0)
        save_json_file(analyzed_scripts, ANALYZED_SCRIPTS_PATH)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key.Key_Return and event.modifiers() & Qt.KeyboardModifier.ControlModifier:
            self.send_chat()

    def check_ctrl_enter(self):
        if QApplication.keyboardModifiers() & Qt.KeyboardModifier.ControlModifier:
            self.send_chat()

    def select_and_read_scripts(self):
        directory = QFileDialog.getExistingDirectory(self, 'Select Directory for Scripts')
        if directory:
            self.selected_folder = directory
            self.selected_folder_label.setText(f'Selected Directory: {directory}')
            self.start_btn.setEnabled(True)

    def start_analysis(self):
        self.start_btn.setEnabled(False)
        self.worker = Worker(self.selected_folder)
        self.worker.progress.connect(self.progress_bar.setValue)
        self.worker.log.connect(self.log_text.append)
        self.worker.finished.connect(self.analysis_finished)
        self.worker.start()
        self.log_text.append("Analysis started...")

    def analysis_finished(self):
        self.log_text.append("Analysis completed.")
        QMessageBox.information(self, "Completed", "Analysis and enhancement completed.")
        self.start_btn.setEnabled(True)

    def send_chat(self):
        message = self.chat_input.text().strip()
        if message:
            self.chat_display.append(f"User: {message}")
            self.chat_input.clear()
            QApplication.processEvents()
            asyncio.run(self.process_chat(message))

    async def process_chat(self, message):
        if message.startswith("analyze_file:"):
            file_path = message.split(":", 1)[1].strip()
            self.analyze_dropped_file(file_path)
            self.chat_display.append(f"AI: Analyzed file {file_path}")
        else:
            urls = [self.url_list.item(i).text() for i in range(self.url_list.count())]
            response = await process_chat_message(message, urls)
            self.chat_display.append(f"AI: {response}")
        QApplication.processEvents()

# Entry point
if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    ex.show()
    sys.exit(app.exec())
 please fix dont weaken or lessen the script just fix it to work much better