package com.openosrs.client.scripting;

import com.openosrs.client.api.AgentAPI;
import com.openosrs.client.core.ClientCore;
import com.openosrs.client.core.EventSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Future;
import java.util.Map;
import java.util.UUID;
import java.util.List;
import java.util.ArrayList;
import java.util.function.Supplier;
import java.util.function.Consumer;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
import java.util.jar.JarEntry;
import java.util.Enumeration;
import java.util.Set;
import java.util.HashSet;
import java.util.HashMap;
import java.util.stream.Collectors;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * Enhanced scripting framework for automated gameplay with advanced agent capabilities.
 */
public class ScriptingFramework {
    private static final Logger logger = LoggerFactory.getLogger(ScriptingFramework.class);
    
    private final AgentAPI agentAPI;
    private final ClientCore clientCore;
    private final ScheduledExecutorService scheduler;
    private final Map<String, RunningScript> activeScripts;
    private final ScriptRegistry scriptRegistry;
    private final ScriptLoader scriptLoader;
    private final ScriptMonitor scriptMonitor;
    private final EventSystem eventSystem;
    private volatile boolean enabled;
    private volatile boolean safeMode;
    private final Object scriptLock = new Object();
    private final Map<String, ScriptExecutionHistory> executionHistory;
    private final Set<String> trustedScriptPaths;
    private final Map<String, Consumer<ScriptEvent>> eventListeners;
    
    public ScriptingFramework(AgentAPI agentAPI, ClientCore clientCore, EventSystem eventSystem) {
        this.agentAPI = agentAPI;
        this.clientCore = clientCore;
        this.eventSystem = eventSystem;
        this.scheduler = Executors.newScheduledThreadPool(8); // Increased thread pool
        this.activeScripts = new ConcurrentHashMap<>();
        this.scriptRegistry = new ScriptRegistry();
        this.scriptLoader = new ScriptLoader();
        this.scriptMonitor = new ScriptMonitor();
        this.enabled = true;
        this.safeMode = true; // Safe mode enabled by default
        this.executionHistory = new ConcurrentHashMap<>();
        this.trustedScriptPaths = new HashSet<>();
        this.eventListeners = new ConcurrentHashMap<>();
        
        initializeBuiltInScripts();
        startMonitoringTasks();
        
        logger.info("Enhanced scripting framework initialized with safe mode: {}", safeMode);
    }
    
    /**
     * Register a script for use.
     */
    public void registerScript(String name, Script script) {
        synchronized (scriptLock) {
            scriptRegistry.register(name, script);
            logger.info("Registered script: {}", name);
            
            // Fire script registration event
            fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_REGISTERED, name, null, null));
        }
    }
    
    /**
     * Load and register scripts from a directory.
     */
    public void loadScriptsFromDirectory(String directoryPath) {
        try {
            Path dir = Paths.get(directoryPath);
            if (!Files.exists(dir) || !Files.isDirectory(dir)) {
                logger.warn("Script directory does not exist: {}", directoryPath);
                return;
            }
            
            List<Script> loadedScripts = scriptLoader.loadFromDirectory(dir);
            for (Script script : loadedScripts) {
                ScriptMetadata metadata = script.getMetadata();
                registerScript(metadata.getName(), script);
            }
            
            logger.info("Loaded {} scripts from directory: {}", loadedScripts.size(), directoryPath);
        } catch (Exception e) {
            logger.error("Failed to load scripts from directory: {}", directoryPath, e);
        }
    }
    
    /**
     * Load and register a script from a JAR file.
     */
    public void loadScriptFromJar(String jarPath) {
        try {
            if (safeMode && !trustedScriptPaths.contains(jarPath)) {
                logger.warn("Cannot load untrusted script in safe mode: {}", jarPath);
                return;
            }
            
            List<Script> loadedScripts = scriptLoader.loadFromJar(Paths.get(jarPath));
            for (Script script : loadedScripts) {
                ScriptMetadata metadata = script.getMetadata();
                registerScript(metadata.getName(), script);
            }
            
            logger.info("Loaded {} scripts from JAR: {}", loadedScripts.size(), jarPath);
        } catch (Exception e) {
            logger.error("Failed to load script from JAR: {}", jarPath, e);
        }
    }
    
    /**
     * Add a trusted script path for safe mode.
     */
    public void addTrustedScriptPath(String path) {
        trustedScriptPaths.add(path);
        logger.info("Added trusted script path: {}", path);
    }
    
    /**
     * Enable or disable safe mode.
     */
    public void setSafeMode(boolean safeMode) {
        this.safeMode = safeMode;
        logger.info("Safe mode {}", safeMode ? "enabled" : "disabled");
    }
    
    public boolean isSafeMode() {
        return safeMode;
    }
    
    /**
     * Start a script execution.
     */
    public String startScript(String scriptName) {
        return startScript(scriptName, new ScriptConfiguration());
    }
    
    /**
     * Start a script execution with configuration.
     */
    public String startScript(String scriptName, ScriptConfiguration config) {
        if (!enabled) {
            throw new IllegalStateException("Scripting framework is disabled");
        }
        
        synchronized (scriptLock) {
            // Check concurrent script limits
            if (activeScripts.size() >= config.getMaxConcurrentScripts()) {
                throw new IllegalStateException("Maximum concurrent scripts reached: " + config.getMaxConcurrentScripts());
            }
            
            Script script = scriptRegistry.getScript(scriptName);
            if (script == null) {
                throw new IllegalArgumentException("Script not found: " + scriptName);
            }
            
            String executionId = UUID.randomUUID().toString();
            ScriptContext context = new ScriptContext(executionId, agentAPI, clientCore, eventSystem, config);
            
            // Create execution history entry
            ScriptExecutionHistory history = new ScriptExecutionHistory(executionId, scriptName, config);
            executionHistory.put(executionId, history);
            
            Future<?> future;
            if (config.isScheduled()) {
                // Schedule recurring execution
                future = scheduler.scheduleAtFixedRate(
                    () -> executeScript(script, context, history),
                    config.getInitialDelay(),
                    config.getRepeatInterval(),
                    TimeUnit.MILLISECONDS
                );
            } else {
                // Single execution
                future = scheduler.submit(() -> executeScript(script, context, history));
            }
            
            RunningScript runningScript = new RunningScript(
                executionId, scriptName, script, context, future, config);
            activeScripts.put(executionId, runningScript);
            
            // Fire script start event
            fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_STARTED, scriptName, executionId, null));
            
            logger.info("Started script: {} with execution ID: {}", scriptName, executionId);
            return executionId;
        }
    }
    
    private void executeScript(Script script, ScriptContext context, ScriptExecutionHistory history) {
        try {
            logger.debug("Executing script: {} ({})", script.getMetadata().getName(), context.getExecutionId());
            
            context.setStatus(ScriptStatus.RUNNING);
            history.recordExecution();
            
            script.execute(context);
            
            if (context.getStatus() == ScriptStatus.RUNNING) {
                context.setStatus(ScriptStatus.COMPLETED);
            }
            
            history.recordCompletion(context.getStatus());
            
            logger.info("Script completed: {} ({})", script.getMetadata().getName(), context.getExecutionId());
            
            fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_COMPLETED, 
                script.getMetadata().getName(), context.getExecutionId(), context.getStatus()));
            
        } catch (InterruptedException e) {
            context.setStatus(ScriptStatus.STOPPED);
            history.recordCompletion(ScriptStatus.STOPPED);
            logger.info("Script interrupted: {} ({})", script.getMetadata().getName(), context.getExecutionId());
            
            fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_STOPPED, 
                script.getMetadata().getName(), context.getExecutionId(), ScriptStatus.STOPPED));
                
        } catch (Exception e) {
            context.setStatus(ScriptStatus.FAILED);
            context.setError(e.getMessage());
            history.recordCompletion(ScriptStatus.FAILED);
            
            logger.error("Script execution failed: {} ({})", script.getMetadata().getName(), context.getExecutionId(), e);
            
            fireScriptEvent(new ScriptEvent(ScriptEventType.SCRIPT_FAILED, 
                script.getMetadata().getName(), context.getExecutionId(), ScriptStatus.FAILED));
            
        } finally {
            // Only remove from active scripts if it's not a scheduled script or if it failed/stopped
            ScriptConfiguration config = context.getConfiguration();
            if (!config.isScheduled() || 
                context.getStatus() == ScriptStatus.FAILED || 
                context.getStatus() == ScriptStatus.STOPPED) {
                activeScripts.remove(context.getExecutionId());
            }
        }
    }
    
    /**
     * Stop a running script.
     */
    public boolean stopScript(String executionId) {
        RunningScript runningScript = activeScripts.get(executionId);
        if (runningScript == null) {
            return false;
        }
        
        runningScript.getContext().stop();
        runningScript.getFuture().cancel(true);
        activeScripts.remove(executionId);
        
        logger.info("Stopped script execution: {}", executionId);
        return true;
    }
    
    /**
     * Get status of a script execution.
     */
    public ScriptStatus getScriptStatus(String executionId) {
        RunningScript runningScript = activeScripts.get(executionId);
        return runningScript != null ? 
            runningScript.getContext().getStatus() : ScriptStatus.NOT_FOUND;
    }
    
    /**
     * Get all active script executions.
     */
    public Map<String, ScriptInfo> getActiveScripts() {
        Map<String, ScriptInfo> result = new ConcurrentHashMap<>();
        
        for (Map.Entry<String, RunningScript> entry : activeScripts.entrySet()) {
            RunningScript rs = entry.getValue();
            result.put(entry.getKey(), new ScriptInfo(
                rs.getExecutionId(),
                rs.getScriptName(),
                rs.getContext().getStatus(),
                rs.getContext().getStartTime(),
                rs.getContext().getError()
            ));
        }
        
        return result;
    }
    
    /**
     * Stop all running scripts.
     */
    public void stopAllScripts() {
        logger.info("Stopping all running scripts");
        
        synchronized (scriptLock) {
            for (RunningScript runningScript : activeScripts.values()) {
                runningScript.getContext().stop();
                runningScript.getFuture().cancel(true);
            }
            
            activeScripts.clear();
        }
    }
    
    /**
     * Get execution history for a script.
     */
    public ScriptExecutionHistory getExecutionHistory(String executionId) {
        return executionHistory.get(executionId);
    }
    
    /**
     * Get all registered scripts.
     */
    public Map<String, ScriptMetadata> getRegisteredScripts() {
        return scriptRegistry.getAllScripts().entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> entry.getValue().getMetadata()
            ));
    }
    
    /**
     * Add an event listener for script events.
     */
    public void addEventListener(String eventType, Consumer<ScriptEvent> listener) {
        eventListeners.put(eventType, listener);
    }
    
    /**
     * Remove an event listener.
     */
    public void removeEventListener(String eventType) {
        eventListeners.remove(eventType);
    }
    
    /**
     * Fire a script event to all listeners.
     */
    private void fireScriptEvent(ScriptEvent event) {
        try {
            // Fire to framework listeners
            Consumer<ScriptEvent> listener = eventListeners.get(event.getType().name());
            if (listener != null) {
                listener.accept(event);
            }
            
            // Fire to global event system
            if (eventSystem != null) {
                eventSystem.fireEvent("script." + event.getType().name().toLowerCase(), event);
            }
        } catch (Exception e) {
            logger.warn("Error firing script event: {}", event, e);
        }
    }
    
    /**
     * Initialize built-in scripts.
     */
    private void initializeBuiltInScripts() {
        // Register any built-in utility scripts here
        logger.debug("Initialized built-in scripts");
    }
    
    /**
     * Start monitoring tasks for script health and cleanup.
     */
    private void startMonitoringTasks() {
        // Schedule cleanup of old execution history
        scheduler.scheduleAtFixedRate(() -> {
            try {
                cleanupExecutionHistory();
            } catch (Exception e) {
                logger.warn("Error during execution history cleanup", e);
            }
        }, 1, 1, TimeUnit.HOURS);
        
        // Schedule script health monitoring
        scheduler.scheduleAtFixedRate(() -> {
            try {
                monitorScriptHealth();
            } catch (Exception e) {
                logger.warn("Error during script health monitoring", e);
            }
        }, 30, 30, TimeUnit.SECONDS);
    }
    
    /**
     * Clean up old execution history entries.
     */
    private void cleanupExecutionHistory() {
        long cutoffTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7); // Keep 7 days
        
        executionHistory.entrySet().removeIf(entry -> {
            ScriptExecutionHistory history = entry.getValue();
            return history.getCreatedTime() < cutoffTime;
        });
    }
    
    /**
     * Monitor health of running scripts.
     */
    private void monitorScriptHealth() {
        for (RunningScript runningScript : activeScripts.values()) {
            ScriptContext context = runningScript.getContext();
            
            // Check for scripts running too long
            long runtime = System.currentTimeMillis() - context.getStartTime();
            ScriptConfiguration config = context.getConfiguration();
            
            if (config.getMaxRuntime() > 0 && runtime > config.getMaxRuntime()) {
                logger.warn("Script {} exceeded maximum runtime, stopping", runningScript.getScriptName());
                stopScript(runningScript.getExecutionId());
            }
        }
    }
    
    /**
     * Enable or disable the scripting framework.
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        if (!enabled) {
            stopAllScripts();
        }
        logger.info("Scripting framework {}", enabled ? "enabled" : "disabled");
    }
    
    public boolean isEnabled() {
        return enabled;
    }
    
    public void shutdown() {
        logger.info("Shutting down scripting framework");
        setEnabled(false);
        scheduler.shutdown();
        
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

// Public classes moved to separate files

/**
 * Internal classes for script management.
 */
class ScriptRegistry {
    private final Map<String, Script> scripts = new ConcurrentHashMap<>();
    
    public void register(String name, Script script) {
        scripts.put(name, script);
    }
    
    public Script getScript(String name) {
        return scripts.get(name);
    }
    
    public Map<String, Script> getAllScripts() {
        return new ConcurrentHashMap<>(scripts);
    }
}

class RunningScript {
    private final String executionId;
    private final String scriptName;
    private final Script script;
    private final ScriptContext context;
    private final Future<?> future;
    private final ScriptConfiguration configuration;
    
    public RunningScript(String executionId, String scriptName, Script script, 
                        ScriptContext context, Future<?> future, ScriptConfiguration configuration) {
        this.executionId = executionId;
        this.scriptName = scriptName;
        this.script = script;
        this.context = context;
        this.future = future;
        this.configuration = configuration;
    }
    
    public String getExecutionId() { return executionId; }
    public String getScriptName() { return scriptName; }
    public Script getScript() { return script; }
    public ScriptContext getContext() { return context; }
    public Future<?> getFuture() { return future; }
    public ScriptConfiguration getConfiguration() { return configuration; }
}

// Remaining public classes also moved to separate files

/**
 * Dynamic script loader for external scripts.
 */
class ScriptLoader {
    private static final Logger logger = LoggerFactory.getLogger(ScriptLoader.class);
    
    public List<Script> loadFromDirectory(Path directory) throws IOException {
        List<Script> scripts = new ArrayList<>();
        
        Files.walk(directory)
            .filter(Files::isRegularFile)
            .filter(path -> path.toString().endsWith(".class"))
            .forEach(classFile -> {
                try {
                    Script script = loadScriptFromClass(classFile);
                    if (script != null) {
                        scripts.add(script);
                    }
                } catch (Exception e) {
                    logger.warn("Failed to load script from: {}", classFile, e);
                }
            });
        
        return scripts;
    }
    
    public List<Script> loadFromJar(Path jarFile) throws IOException {
        List<Script> scripts = new ArrayList<>();
        
        try (JarFile jar = new JarFile(jarFile.toFile());
             URLClassLoader classLoader = new URLClassLoader(new URL[]{jarFile.toUri().toURL()})) {
            
            Enumeration<JarEntry> entries = jar.entries();
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                
                if (entry.getName().endsWith(".class")) {
                    try {
                        String className = entry.getName()
                            .replace('/', '.')
                            .substring(0, entry.getName().length() - 6);
                        
                        Class<?> clazz = classLoader.loadClass(className);
                        
                        if (Script.class.isAssignableFrom(clazz)) {
                            Constructor<?> constructor = clazz.getDeclaredConstructor();
                            Script script = (Script) constructor.newInstance();
                            scripts.add(script);
                        }
                    } catch (Exception e) {
                        logger.debug("Skipping class {}: {}", entry.getName(), e.getMessage());
                    }
                }
            }
        }
        
        return scripts;
    }
    
    private Script loadScriptFromClass(Path classFile) {
        // Implementation would depend on specific requirements
        // This is a placeholder for dynamic class loading
        return null;
    }
}

/**
 * Script monitoring and management utilities.
 */
class ScriptMonitor {
    private static final Logger logger = LoggerFactory.getLogger(ScriptMonitor.class);
    
    public void analyzePerformance(Map<String, RunningScript> activeScripts) {
        for (RunningScript script : activeScripts.values()) {
            ScriptContext context = script.getContext();
            long runtime = System.currentTimeMillis() - context.getStartTime();
            long inactivity = System.currentTimeMillis() - context.getLastActivityTime();
            
            if (inactivity > TimeUnit.MINUTES.toMillis(5)) {
                logger.warn("Script {} appears inactive for {} ms", 
                    script.getScriptName(), inactivity);
            }
            
            if (runtime > TimeUnit.HOURS.toMillis(1)) {
                logger.info("Long-running script {} has been active for {} ms", 
                    script.getScriptName(), runtime);
            }
        }
    }
}