package com.openosrs.client.login;

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

import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * GameConnectionManager - Handles network connections to RuneScape servers.
 * 
 * This class manages:
 * - Connection to RuneScape game servers
 * - World selection and optimization
 * - Network state monitoring and ping calculation
 * - Authentication protocol handling
 * - Auto-reconnection functionality
 * 
 * Designed to provide reliable connectivity for AI agents.
 */
public class GameConnectionManager {
    private static final Logger logger = LoggerFactory.getLogger(GameConnectionManager.class);
    
    // RuneScape server information
    private static final String[] DEFAULT_SERVERS = {
        "oldschool1.runescape.com",
        "oldschool2.runescape.com", 
        "oldschool3.runescape.com"
    };
    
    private static final int DEFAULT_PORT = 43594;
    private static final int PING_TIMEOUT_MS = 5000;
    private static final int CONNECTION_TIMEOUT_MS = 10000;
    
    // World information
    private static final Map<Integer, WorldInfo> WORLD_MAP = createWorldMap();
    
    private final ClientCore clientCore;
    private volatile boolean connected = false;
    private volatile int currentWorld = -1;
    private volatile int currentPing = -1;
    private volatile boolean autoReconnect = false;
    private Socket gameSocket;
    private Thread connectionMonitorThread;
    
    /**
     * Create a new GameConnectionManager.
     */
    public GameConnectionManager(ClientCore clientCore) {
        this.clientCore = clientCore;
        logger.info("GameConnectionManager initialized");
    }
    
    /**
     * Connect to RuneScape servers.
     * 
     * @param timeoutSeconds Maximum time to wait for connection
     * @return true if connected successfully
     */
    public boolean connect(int timeoutSeconds) {
        if (connected) {
            logger.info("Already connected to game servers");
            return true;
        }
        
        logger.info("Connecting to RuneScape servers...");
        
        // Try to connect to available servers
        for (String server : DEFAULT_SERVERS) {
            try {
                if (attemptConnection(server, DEFAULT_PORT, timeoutSeconds)) {
                    connected = true;
                    logger.info("Successfully connected to server: {}", server);
                    startConnectionMonitoring();
                    return true;
                }
            } catch (Exception e) {
                logger.warn("Failed to connect to server {}: {}", server, e.getMessage());
            }
        }
        
        logger.error("Failed to connect to any RuneScape servers");
        return false;
    }
    
    /**
     * Attempt connection to a specific server.
     */
    private boolean attemptConnection(String server, int port, int timeoutSeconds) throws IOException {
        logger.debug("Attempting connection to {}:{}", server, port);
        
        SocketAddress endpoint = new InetSocketAddress(server, port);
        gameSocket = new Socket();
        
        try {
            gameSocket.connect(endpoint, timeoutSeconds * 1000);
            
            // Test basic connectivity
            if (gameSocket.isConnected() && !gameSocket.isClosed()) {
                logger.debug("Socket connected to {}", server);
                return true;
            }
        } catch (IOException e) {
            if (gameSocket != null) {
                try {
                    gameSocket.close();
                } catch (IOException closeEx) {
                    // Ignore close exception
                }
                gameSocket = null;
            }
            throw e;
        }
        
        return false;
    }
    
    /**
     * Select the optimal world for gameplay.
     * 
     * @return world number, or -1 if no suitable world found
     */
    public int selectOptimalWorld() {
        if (!connected) {
            logger.error("Cannot select world - not connected to servers");
            return -1;
        }
        
        logger.info("Selecting optimal world...");
        
        // Get list of available worlds and test them
        List<WorldInfo> availableWorlds = getAvailableWorlds();
        WorldInfo bestWorld = null;
        int bestScore = Integer.MAX_VALUE;
        
        for (WorldInfo world : availableWorlds) {
            try {
                int ping = pingWorld(world);
                int playerCount = world.getPlayerCount();
                
                // Calculate world score (lower is better)
                // Prefer lower ping and moderate population
                int score = ping + (playerCount > 1500 ? playerCount - 1500 : 0);
                
                logger.debug("World {}: ping={}ms, players={}, score={}", 
                    world.getId(), ping, playerCount, score);
                
                if (score < bestScore) {
                    bestScore = score;
                    bestWorld = world;
                }
                
                // If we find a really good world, use it immediately
                if (ping < 50 && playerCount < 1000) {
                    bestWorld = world;
                    break;
                }
                
            } catch (Exception e) {
                logger.debug("Failed to test world {}: {}", world.getId(), e.getMessage());
            }
        }
        
        if (bestWorld != null) {
            currentWorld = bestWorld.getId();
            currentPing = pingWorld(bestWorld);
            logger.info("Selected world {} (ping: {}ms, players: {})", 
                currentWorld, currentPing, bestWorld.getPlayerCount());
            return currentWorld;
        }
        
        logger.error("No suitable world found");
        return -1;
    }
    
    /**
     * Get list of available worlds.
     */
    private List<WorldInfo> getAvailableWorlds() {
        // In a real implementation, this would fetch from RuneScape API
        // For now, return a subset of known worlds
        List<WorldInfo> worlds = new ArrayList<>();
        
        // Add some common F2P and P2P worlds
        worlds.add(WORLD_MAP.get(301)); // F2P
        worlds.add(WORLD_MAP.get(308)); // F2P
        worlds.add(WORLD_MAP.get(316)); // F2P
        worlds.add(WORLD_MAP.get(335)); // P2P
        worlds.add(WORLD_MAP.get(420)); // P2P
        worlds.add(WORLD_MAP.get(444)); // P2P
        
        // Filter out null entries
        worlds.removeIf(Objects::isNull);
        
        return worlds;
    }
    
    /**
     * Ping a specific world to test latency.
     */
    private int pingWorld(WorldInfo world) {
        try {
            String serverAddress = "oldschool" + (world.getId() - 300) + ".runescape.com";
            
            long startTime = System.currentTimeMillis();
            
            // Simple TCP connection test
            try (Socket pingSocket = new Socket()) {
                SocketAddress endpoint = new InetSocketAddress(serverAddress, DEFAULT_PORT);
                pingSocket.connect(endpoint, PING_TIMEOUT_MS);
                
                long endTime = System.currentTimeMillis();
                return (int) (endTime - startTime);
            }
            
        } catch (Exception e) {
            logger.debug("Failed to ping world {}: {}", world.getId(), e.getMessage());
            return 9999; // High ping for failed connections
        }
    }
    
    /**
     * Authenticate with the RuneScape servers using credentials.
     * 
     * @param credentials Login credentials to use
     * @return true if authentication successful
     */
    public boolean authenticate(LoginCredentials credentials) {
        if (!connected || currentWorld <= 0) {
            logger.error("Cannot authenticate - not connected or no world selected");
            return false;
        }
        
        if (!credentials.isValid()) {
            logger.error("Cannot authenticate - invalid credentials");
            return false;
        }
        
        logger.info("Authenticating with RuneScape servers...");
        
        try {
            // In a real implementation, this would:
            // 1. Send login packet with username/password
            // 2. Handle server response codes
            // 3. Process any additional authentication steps
            // 4. Validate login success
            
            // For now, simulate authentication process
            return simulateAuthentication(credentials);
            
        } catch (Exception e) {
            logger.error("Authentication failed", e);
            return false;
        }
    }
    
    /**
     * Simulate the authentication process (placeholder for real implementation).
     */
    private boolean simulateAuthentication(LoginCredentials credentials) {
        try {
            // Simulate network delay
            Thread.sleep(1000 + new Random().nextInt(2000));
            
            // Basic credential validation (in real implementation, server validates)
            String username = credentials.getUsername();
            String password = credentials.getPasswordAsString();
            
            // Simulate some basic validation
            if (username == null || username.length() < 3 || 
                password == null || password.length() < 5) {
                logger.warn("Authentication failed - credentials rejected by server");
                return false;
            }
            
            // Simulate successful authentication
            logger.info("Authentication successful for user: {}", 
                username.substring(0, Math.min(3, username.length())) + "***");
            return true;
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    /**
     * Start monitoring the connection status.
     */
    private void startConnectionMonitoring() {
        if (connectionMonitorThread != null && connectionMonitorThread.isAlive()) {
            return; // Already monitoring
        }
        
        connectionMonitorThread = new Thread(this::monitorConnection, "Connection-Monitor");
        connectionMonitorThread.setDaemon(true);
        connectionMonitorThread.start();
        
        logger.debug("Connection monitoring started");
    }
    
    /**
     * Monitor connection status and handle disconnections.
     */
    private void monitorConnection() {
        while (connected && !Thread.currentThread().isInterrupted()) {
            try {
                Thread.sleep(5000); // Check every 5 seconds
                
                // Test connection
                if (gameSocket == null || gameSocket.isClosed() || !gameSocket.isConnected()) {
                    logger.warn("Connection lost to game server");
                    handleDisconnection();
                    break;
                }
                
                // Update ping
                if (currentWorld > 0) {
                    WorldInfo world = WORLD_MAP.get(currentWorld);
                    if (world != null) {
                        currentPing = pingWorld(world);
                    }
                }
                
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                logger.warn("Error in connection monitoring", e);
            }
        }
        
        logger.debug("Connection monitoring stopped");
    }
    
    /**
     * Handle disconnection and attempt reconnection if enabled.
     */
    private void handleDisconnection() {
        connected = false;
        
        if (autoReconnect) {
            logger.info("Attempting auto-reconnection...");
            
            CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS).execute(() -> {
                if (!connected) {
                    connect(30); // Try to reconnect with 30 second timeout
                }
            });
        }
    }
    
    /**
     * Disconnect from the game servers.
     */
    public void disconnect() {
        logger.info("Disconnecting from game servers...");
        
        connected = false;
        currentWorld = -1;
        currentPing = -1;
        
        // Stop monitoring
        if (connectionMonitorThread != null) {
            connectionMonitorThread.interrupt();
        }
        
        // Close socket
        if (gameSocket != null) {
            try {
                gameSocket.close();
            } catch (IOException e) {
                logger.debug("Error closing game socket", e);
            }
            gameSocket = null;
        }
        
        logger.info("Disconnected from game servers");
    }
    
    /**
     * Check if currently connected to game servers.
     */
    public boolean isConnected() {
        return connected && gameSocket != null && 
               gameSocket.isConnected() && !gameSocket.isClosed();
    }
    
    /**
     * Get the currently selected world.
     */
    public int getCurrentWorld() {
        return currentWorld;
    }
    
    /**
     * Get the current ping to the game server.
     */
    public int getPing() {
        return currentPing;
    }
    
    /**
     * Enable or disable auto-reconnection.
     */
    public void setAutoReconnect(boolean autoReconnect) {
        this.autoReconnect = autoReconnect;
        logger.info("Auto-reconnect {}", autoReconnect ? "enabled" : "disabled");
    }
    
    /**
     * Create the world map with known world information.
     */
    private static Map<Integer, WorldInfo> createWorldMap() {
        Map<Integer, WorldInfo> map = new HashMap<>();
        
        // Add some common worlds (simplified data)
        map.put(301, new WorldInfo(301, "World 301", "USA", false, 1200));
        map.put(308, new WorldInfo(308, "World 308", "UK", false, 1100));
        map.put(316, new WorldInfo(316, "World 316", "USA", false, 1300));
        map.put(335, new WorldInfo(335, "World 335", "UK", true, 800));
        map.put(420, new WorldInfo(420, "World 420", "AUS", true, 600));
        map.put(444, new WorldInfo(444, "World 444", "USA", true, 900));
        
        return map;
    }
    
    /**
     * World information container.
     */
    public static class WorldInfo {
        private final int id;
        private final String name;
        private final String region;
        private final boolean members;
        private final int playerCount;
        
        public WorldInfo(int id, String name, String region, boolean members, int playerCount) {
            this.id = id;
            this.name = name;
            this.region = region;
            this.members = members;
            this.playerCount = playerCount;
        }
        
        public int getId() { return id; }
        public String getName() { return name; }
        public String getRegion() { return region; }
        public boolean isMembers() { return members; }
        public int getPlayerCount() { return playerCount; }
        
        @Override
        public String toString() {
            return String.format("World{id=%d, name='%s', region='%s', members=%s, players=%d}", 
                id, name, region, members, playerCount);
        }
    }
}