package party.cybsec.griefdetect.core;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockFromToEvent;
import org.bukkit.plugin.java.JavaPlugin;
import party.cybsec.griefdetect.GriefDetectPlugin;
import party.cybsec.griefdetect.config.PluginConfig;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Async detection engine that processes events and evaluates clusters.
 * Zero per-tick work, async-first processing with event capture on main thread.
 */
public class DetectionEngine implements Listener {
    
    private final GriefDetectPlugin plugin;
    private final PluginConfig config;
    private final ScheduledExecutorService asyncExecutor;
    
    // Cluster tracking
    private final Map<String, Cluster> activeClusters;
    private final Map<UUID, PlayerPosition> playerPositions;
    
    // Configuration
    private final int analysisInterval;
    private final int clusterTimeout;
    private final int maxClustersPerWorld;
    
    // Throttling
    private final Map<UUID, Long> playerPositionUpdateCooldown;
    private static final long POSITION_UPDATE_COOLDOWN_MS = 5000;
    
    public DetectionEngine(GriefDetectPlugin plugin) {
        this.plugin = plugin;
        this.config = plugin.getPluginConfig();
        this.asyncExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "GriefDetect-Async");
            t.setDaemon(true);
            return t;
        });
        this.activeClusters = new ConcurrentHashMap<>();
        this.playerPositions = new ConcurrentHashMap<>();
        this.playerPositionUpdateCooldown = new ConcurrentHashMap<>();
        
        // Load configuration
        this.analysisInterval = config.getEngineAnalysisInterval();
        this.clusterTimeout = config.getEngineClusterTimeout();
        this.maxClustersPerWorld = config.getEngineMaxClustersPerWorld();
        
        // Register events
        Bukkit.getPluginManager().registerEvents(this, plugin);
    }
    
    /**
     * Start the detection engine.
     */
    public void start() {
        // Schedule async analysis
        asyncExecutor.scheduleAtFixedRate(this::performAsyncAnalysis, analysisInterval, analysisInterval, TimeUnit.MILLISECONDS);
        
        // Schedule cleanup
        asyncExecutor.scheduleAtFixedRate(this::cleanupInactiveClusters, 60, 60, TimeUnit.SECONDS);
        
        plugin.getLogger().info("Detection engine started with " + analysisInterval + "ms analysis interval");
    }
    
    /**
     * Shutdown the detection engine.
     */
    public void shutdown() {
        asyncExecutor.shutdown();
        try {
            if (!asyncExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
                asyncExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            asyncExecutor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * Event capture methods - minimal processing on main thread.
     */
    
    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onBlockFromTo(BlockFromToEvent event) {
        // Capture lava-water interactions
        if (event.getBlock().getType().name().contains("LAVA") && 
            (event.getToBlock().getType().name().contains("WATER") || 
             event.getToBlock().getType().name().contains("ICE"))) {
            processBlockEvent(event.getBlock(), event.getToBlock().getLocation());
        }
    }
    
    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onBlockForm(BlockFormEvent event) {
        // Capture cobblestone/stone/obsidian formation
        String blockType = event.getNewState().getType().name();
        if (blockType.contains("COBBLESTONE") || blockType.contains("STONE") || blockType.contains("OBSIDIAN")) {
            processBlockEvent(event.getBlock(), event.getBlock().getLocation());
        }
    }
    
    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
    public void onBlockBreak(BlockBreakEvent event) {
        // Track mining activity for suppression ratio
        processMiningEvent(event.getBlock());
        
        // Update player position
        schedulePlayerPositionUpdate(event.getPlayer());
    }
    
    /**
     * Process block events and update clusters.
     */
    private void processBlockEvent(Block block, Location location) {
        String clusterKey = getClusterKey(location);
        Cluster cluster = activeClusters.computeIfAbsent(clusterKey, k -> new Cluster(location));
        
        cluster.addBlock(location);
        cluster.setLastActivity(System.currentTimeMillis());
    }
    
    /**
     * Process mining events for suppression ratio calculation.
     */
    private void processMiningEvent(Block block) {
        String clusterKey = getClusterKey(block.getLocation());
        Cluster cluster = activeClusters.get(clusterKey);
        
        if (cluster != null) {
            cluster.incrementMiningActivity();
            cluster.setLastActivity(System.currentTimeMillis());
        }
    }
    
    /**
     * Schedule player position update with throttling.
     */
    private void schedulePlayerPositionUpdate(Player player) {
        UUID playerId = player.getUniqueId();
        long now = System.currentTimeMillis();
        
        if (now - playerPositionUpdateCooldown.getOrDefault(playerId, 0L) > POSITION_UPDATE_COOLDOWN_MS) {
            playerPositionUpdateCooldown.put(playerId, now);
            
            // Update position asynchronously to avoid main thread work
            Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
                playerPositions.put(playerId, new PlayerPosition(player.getLocation(), player.getGameMode().name().equals("SURVIVAL")));
            });
        }
    }
    
    /**
     * Perform async analysis of active clusters.
     */
    private void performAsyncAnalysis() {
        try {
            List<Cluster> clustersToEvaluate = new ArrayList<>(activeClusters.values());
            
            for (Cluster cluster : clustersToEvaluate) {
                if (cluster.shouldEvaluate()) {
                    evaluateCluster(cluster);
                }
            }
        } catch (Exception e) {
            plugin.getLogger().severe("Error in async analysis: " + e.getMessage());
        }
    }
    
    /**
     * Evaluate a cluster for grief detection.
     */
    private void evaluateCluster(Cluster cluster) {
        // Calculate confidence
        double confidence = plugin.getConfidenceEngine().calculateConfidence(
            "LavaCast", 
            cluster.getWorld(),
            cluster.getCenter(),
            cluster.getMinX(), cluster.getMinY(), cluster.getMinZ(),
            cluster.getMaxX(), cluster.getMaxY(), cluster.getMaxZ(),
            cluster.getBlockCount(),
            cluster.getDuration()
        );
        
        // Check thresholds
        if (plugin.getConfidenceEngine().shouldIgnore(confidence)) {
            return;
        }
        
        // Get nearest players
        List<DetectionEvent.NearestPlayerInfo> nearestPlayers = getNearestPlayers(cluster.getCenter());
        
        // Create detection event
        DetectionEvent event = new DetectionEvent(
            "LavaCast",
            cluster.getWorld(),
            cluster.getCenter(),
            cluster.getMinX(), cluster.getMinY(), cluster.getMinZ(),
            cluster.getMaxX(), cluster.getMaxY(), cluster.getMaxZ(),
            cluster.getBlockCount(),
            confidence,
            nearestPlayers
        );
        
        // Handle reporting
        if (plugin.getConfidenceEngine().shouldAlertChat(confidence)) {
            // Create ChatAlertManager instance for sending alerts
            plugin.getChatAlertManager().sendAlert(event);
        }
        
        if (plugin.getConfidenceEngine().shouldWebhook(confidence)) {
            plugin.getWebhookManager().sendWebhook(event);
        }
        
        // Reset cluster for next evaluation
        cluster.resetEvaluation();
    }
    
    /**
     * Get nearest players for reporting.
     */
    private List<DetectionEvent.NearestPlayerInfo> getNearestPlayers(Location center) {
        List<DetectionEvent.NearestPlayerInfo> players = new ArrayList<>();
        World world = center.getWorld();
        
        for (Map.Entry<UUID, PlayerPosition> entry : playerPositions.entrySet()) {
            PlayerPosition pos = entry.getValue();
            if (pos.getWorld().equals(world)) {
                double distance = pos.getLocation().distance(center);
                if (distance <= 50.0) { // Within reporting radius
                    players.add(new DetectionEvent.NearestPlayerInfo(
                        entry.getKey(),
                        Bukkit.getPlayer(entry.getKey()).getName(),
                        distance,
                        pos.isSurvival()
                    ));
                }
            }
        }
        
        // Sort by distance
        players.sort((a, b) -> Double.compare(a.getDistance(), b.getDistance()));
        
        return players;
    }
    
    /**
     * Cleanup inactive clusters.
     */
    private void cleanupInactiveClusters() {
        long now = System.currentTimeMillis();
        Iterator<Map.Entry<String, Cluster>> iterator = activeClusters.entrySet().iterator();
        
        while (iterator.hasNext()) {
            Map.Entry<String, Cluster> entry = iterator.next();
            Cluster cluster = entry.getValue();
            
            if (now - cluster.getLastActivity() > clusterTimeout) {
                iterator.remove();
            }
        }
    }
    
    /**
     * Get cluster key for spatial hashing.
     */
    private String getClusterKey(Location location) {
        // Use chunk-based spatial hashing
        int chunkX = location.getBlockX() >> 4;
        int chunkZ = location.getBlockZ() >> 4;
        return location.getWorld().getName() + ":" + chunkX + ":" + chunkZ;
    }
    
    /**
     * Cluster tracking class.
     */
    private static class Cluster {
        private final Location center;
        private final World world;
        private int minX, minY, minZ;
        private int maxX, maxY, maxZ;
        private int blockCount;
        private int miningActivity;
        private long firstActivity;
        private long lastActivity;
        
        public Cluster(Location location) {
            this.center = location;
            this.world = location.getWorld();
            this.minX = this.maxX = location.getBlockX();
            this.minY = this.maxY = location.getBlockY();
            this.minZ = this.maxZ = location.getBlockZ();
            this.blockCount = 0;
            this.miningActivity = 0;
            this.firstActivity = System.currentTimeMillis();
            this.lastActivity = System.currentTimeMillis();
        }
        
        public void addBlock(Location location) {
            blockCount++;
            updateBounds(location);
        }
        
        public void incrementMiningActivity() {
            miningActivity++;
        }
        
        public void setLastActivity(long time) {
            lastActivity = time;
        }
        
        public void resetEvaluation() {
            firstActivity = System.currentTimeMillis();
            miningActivity = 0;
        }
        
        public boolean shouldEvaluate() {
            // Evaluate if enough blocks or enough time has passed
            return blockCount >= 10 || (System.currentTimeMillis() - firstActivity) >= 5000;
        }
        
        private void updateBounds(Location location) {
            minX = Math.min(minX, location.getBlockX());
            minY = Math.min(minY, location.getBlockY());
            minZ = Math.min(minZ, location.getBlockZ());
            maxX = Math.max(maxX, location.getBlockX());
            maxY = Math.max(maxY, location.getBlockY());
            maxZ = Math.max(maxZ, location.getBlockZ());
        }
        
        // Getters
        public Location getCenter() { return center; }
        public World getWorld() { return world; }
        public int getMinX() { return minX; }
        public int getMinY() { return minY; }
        public int getMinZ() { return minZ; }
        public int getMaxX() { return maxX; }
        public int getMaxY() { return maxY; }
        public int getMaxZ() { return maxZ; }
        public int getBlockCount() { return blockCount; }
        public int getMiningActivity() { return miningActivity; }
        public long getFirstActivity() { return firstActivity; }
        public long getLastActivity() { return lastActivity; }
        public long getDuration() { return lastActivity - firstActivity; }
    }
    
    /**
     * Player position tracking class.
     */
    private static class PlayerPosition {
        private final Location location;
        private final boolean isSurvival;
        
        public PlayerPosition(Location location, boolean isSurvival) {
            this.location = location;
            this.isSurvival = isSurvival;
        }
        
        public Location getLocation() { return location; }
        public boolean isSurvival() { return isSurvival; }
        public World getWorld() { return location.getWorld(); }
    }
}