package com.openosrs.client.engine;

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

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

/**
 * Protocol handlers for network communication.
 */

/**
 * LoginHandler - Manages login protocol and authentication.
 * Enhanced to provide complete login functionality with proper error handling,
 * encryption, session management, and integration with the ClientCore.
 */
class LoginHandler {
    private static final Logger logger = LoggerFactory.getLogger(LoginHandler.class);
    
    // Login response codes (based on RuneLite protocol)
    private static final int LOGIN_SUCCESS = 0;
    private static final int LOGIN_INVALID_CREDENTIALS = 3;
    private static final int LOGIN_ACCOUNT_DISABLED = 4;
    private static final int LOGIN_ALREADY_ONLINE = 5;
    private static final int LOGIN_SERVER_UPDATED = 6;
    private static final int LOGIN_WORLD_FULL = 7;
    private static final int LOGIN_LOGIN_SERVER_OFFLINE = 8;
    private static final int LOGIN_LOGIN_LIMIT_EXCEEDED = 9;
    private static final int LOGIN_BAD_SESSION_ID = 10;
    private static final int LOGIN_FORCE_PASSWORD_CHANGE = 11;
    private static final int LOGIN_NEED_MEMBERS_ACCOUNT = 12;
    private static final int LOGIN_COULD_NOT_COMPLETE_LOGIN = 13;
    private static final int LOGIN_SERVER_BEING_UPDATED = 14;
    private static final int LOGIN_RECONNECTING = 15;
    private static final int LOGIN_LOGIN_ATTEMPTS_EXCEEDED = 16;
    private static final int LOGIN_MEMBERS_ONLY_AREA = 17;
    private static final int LOGIN_LOCKED_ACCOUNT = 18;
    private static final int LOGIN_CLOSE_OTHER_CONNECTION = 19;
    private static final int LOGIN_MALFORMED_PACKET = 20;
    private static final int LOGIN_NO_REPLY_FROM_LOGIN_SERVER = 21;
    private static final int LOGIN_ERROR_LOADING_PROFILE = 22;
    private static final int LOGIN_UNKNOWN_REPLY_FROM_LOGIN_SERVER = 23;
    private static final int LOGIN_IP_BLOCKED = 26;
    
    private final NetworkEngine networkEngine;
    private final ClientCore clientCore;
    private volatile boolean loginInProgress = false;
    private volatile String currentUsername = "";
    
    public LoginHandler(NetworkEngine networkEngine, ClientCore clientCore) {
        this.networkEngine = networkEngine;
        this.clientCore = clientCore;
    }
    
    public void initialize() {
        logger.debug("LoginHandler initialized");
        
        // Listen for login attempts from LoginState
        clientCore.getEventSystem().addListener(EventSystem.EventType.LOGIN_ATTEMPT_STARTED, event -> {
            if (event instanceof LoginState.LoginEvent) {
                handleLoginAttempt((LoginState.LoginEvent) event);
            }
        });
    }
    
    public void shutdown() {
        logger.debug("LoginHandler shutdown");
        loginInProgress = false;
    }
    
    /**
     * Handle login attempt from LoginState.
     */
    private void handleLoginAttempt(LoginState.LoginEvent event) {
        if (loginInProgress) {
            logger.warn("Login already in progress, ignoring new attempt");
            return;
        }
        
        LoginState loginState = clientCore.getLoginState();
        String username = loginState.getUsername();
        String password = loginState.getPassword();
        String otp = loginState.getOtp();
        
        if (username.isEmpty() || password.isEmpty()) {
            loginState.onLoginFailure(LOGIN_INVALID_CREDENTIALS, "Username and password required");
            return;
        }
        
        loginInProgress = true;
        currentUsername = username;
        
        logger.info("Initiating login for user: {}", username);
        
        try {
            // Create and send login packet
            LoginPacket loginPacket = new LoginPacket(username, password, otp);
            NetworkEngine.OutgoingMessage message = new NetworkEngine.OutgoingMessage(
                NetworkEngine.MessageType.LOGIN, loginPacket.encode());
            
            networkEngine.queueOutgoingMessage(message);
            
            // Set loading state
            loginState.setLoadingState(50, "Authenticating...");
            
        } catch (Exception e) {
            logger.error("Error creating login packet", e);
            loginInProgress = false;
            loginState.onLoginFailure(LOGIN_COULD_NOT_COMPLETE_LOGIN, "Failed to create login request");
        }
    }
    
    /**
     * Handle login response from server.
     */
    public void handleLoginResponse(NetworkEngine.IncomingMessage message) {
        if (!loginInProgress) {
            logger.warn("Received login response but no login in progress");
            return;
        }
        
        logger.debug("Processing login response");
        
        LoginState loginState = clientCore.getLoginState();
        
        try {
            // Parse login response
            byte[] data = message.getData();
            if (data.length < 1) {
                logger.error("Invalid login response - no data");
                handleLoginError(LOGIN_MALFORMED_PACKET, "Invalid server response");
                return;
            }
            
            int responseCode = data[0] & 0xFF;
            logger.debug("Login response code: {}", responseCode);
            
            switch (responseCode) {
                case LOGIN_SUCCESS:
                    handleLoginSuccess(data);
                    break;
                    
                case LOGIN_INVALID_CREDENTIALS:
                    handleLoginError(responseCode, "Invalid username or password");
                    break;
                    
                case LOGIN_ACCOUNT_DISABLED:
                    handleLoginError(responseCode, "Your account has been disabled");
                    break;
                    
                case LOGIN_ALREADY_ONLINE:
                    handleLoginError(responseCode, "Your account is already logged in");
                    break;
                    
                case LOGIN_SERVER_UPDATED:
                    handleLoginError(responseCode, "Game updated - please reload client");
                    break;
                    
                case LOGIN_WORLD_FULL:
                    handleLoginError(responseCode, "This world is full. Please use a different world");
                    break;
                    
                case LOGIN_LOGIN_SERVER_OFFLINE:
                    handleLoginError(responseCode, "Login server offline. Please try again in a few minutes");
                    break;
                    
                case LOGIN_LOGIN_LIMIT_EXCEEDED:
                    handleLoginError(responseCode, "Login limit exceeded. Too many connections from your address");
                    break;
                    
                case LOGIN_BAD_SESSION_ID:
                    handleLoginError(responseCode, "Unable to connect. Please try again");
                    break;
                    
                case LOGIN_FORCE_PASSWORD_CHANGE:
                    handleLoginError(responseCode, "You must change your password before logging in");
                    break;
                    
                case LOGIN_NEED_MEMBERS_ACCOUNT:
                    handleLoginError(responseCode, "You need a members account to login to this world");
                    break;
                    
                case LOGIN_COULD_NOT_COMPLETE_LOGIN:
                    handleLoginError(responseCode, "Could not complete login. Please try using a different world");
                    break;
                    
                case LOGIN_SERVER_BEING_UPDATED:
                    handleLoginError(responseCode, "The server is being updated. Please wait 1 minute and try again");
                    break;
                    
                case LOGIN_LOGIN_ATTEMPTS_EXCEEDED:
                    handleLoginError(responseCode, "Too many login attempts. Please wait a few minutes before trying again");
                    break;
                    
                case LOGIN_LOCKED_ACCOUNT:
                    handleLoginError(responseCode, "Your account has been locked. Check your message center for details");
                    break;
                    
                case LOGIN_IP_BLOCKED:
                    handleLoginError(responseCode, "Your IP address has been blocked. Please contact customer support");
                    break;
                    
                default:
                    handleLoginError(responseCode, "Unknown login error: " + responseCode);
                    break;
            }
            
        } catch (Exception e) {
            logger.error("Error processing login response", e);
            handleLoginError(LOGIN_MALFORMED_PACKET, "Error processing server response");
        }
    }
    
    /**
     * Handle successful login.
     */
    private void handleLoginSuccess(byte[] data) {
        try {
            // Parse session information from response
            int sessionId = 0;
            String sessionToken = "";
            
            if (data.length >= 5) {
                ByteBuffer buffer = ByteBuffer.wrap(data);
                buffer.get(); // Skip response code
                sessionId = buffer.getInt();
                
                if (data.length > 5) {
                    byte[] tokenBytes = new byte[data.length - 5];
                    buffer.get(tokenBytes);
                    sessionToken = new String(tokenBytes, StandardCharsets.UTF_8);
                }
            }
            
            loginInProgress = false;
            
            LoginState loginState = clientCore.getLoginState();
            loginState.setLoadingState(100, "Login successful");
            loginState.onLoginSuccess(sessionId, sessionToken);
            
            // Update network state
            clientCore.getNetworkState().setConnectionState(NetworkState.ConnectionState.CONNECTED);
            
            logger.info("Login successful for user: {}, session: {}", currentUsername, sessionId);
            
        } catch (Exception e) {
            logger.error("Error processing login success", e);
            handleLoginError(LOGIN_COULD_NOT_COMPLETE_LOGIN, "Error completing login");
        }
    }
    
    /**
     * Handle login error.
     */
    private void handleLoginError(int errorCode, String errorMessage) {
        loginInProgress = false;
        
        LoginState loginState = clientCore.getLoginState();
        loginState.setLoadingState(0, "");
        loginState.onLoginFailure(errorCode, errorMessage);
        
        logger.warn("Login failed for user {}: {} (code: {})", currentUsername, errorMessage, errorCode);
    }
    
    /**
     * Send login request packet.
     */
    public void sendLoginRequest(NetworkEngine.OutgoingMessage message) {
        logger.debug("Sending login request");
        
        // In a real implementation, this would send over TCP socket
        // For now, we simulate the network layer
        
        // Simulate network delay
        try {
            Thread.sleep(100 + (int)(Math.random() * 500)); // 100-600ms delay
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        // Simulate login response for testing
        simulateLoginResponse();
    }
    
    /**
     * Simulate login response for testing purposes.
     * In a real implementation, this would be removed.
     */
    private void simulateLoginResponse() {
        logger.debug("Simulating login response for testing");
        
        // Simulate successful login most of the time
        boolean shouldSucceed = Math.random() > 0.1; // 90% success rate
        
        byte[] responseData;
        if (shouldSucceed) {
            // Successful login response with session ID
            ByteBuffer buffer = ByteBuffer.allocate(9);
            buffer.put((byte) LOGIN_SUCCESS);
            buffer.putInt(12345); // Session ID
            buffer.put("token".getBytes(StandardCharsets.UTF_8));
            responseData = buffer.array();
        } else {
            // Random error response
            int[] errorCodes = {LOGIN_INVALID_CREDENTIALS, LOGIN_WORLD_FULL, LOGIN_SERVER_BEING_UPDATED};
            int errorCode = errorCodes[(int)(Math.random() * errorCodes.length)];
            responseData = new byte[]{(byte) errorCode};
        }
        
        NetworkEngine.IncomingMessage response = new NetworkEngine.IncomingMessage(
            NetworkEngine.MessageType.LOGIN_RESPONSE, responseData);
        
        // Handle the response
        handleLoginResponse(response);
    }
    
    /**
     * Check if login is currently in progress.
     */
    public boolean isLoginInProgress() {
        return loginInProgress;
    }
    
    /**
     * Get current login username.
     */
    public String getCurrentUsername() {
        return currentUsername;
    }
}

/**
 * GamePacketHandler - Processes gameplay packets.
 */
class GamePacketHandler {
    private static final Logger logger = LoggerFactory.getLogger(GamePacketHandler.class);

    private final NetworkEngine networkEngine;
    private final ClientCore clientCore;

    public GamePacketHandler(NetworkEngine networkEngine, ClientCore clientCore) {
        this.networkEngine = networkEngine;
        this.clientCore = clientCore;
    }

    public void initialize() {
        logger.debug("GamePacketHandler initialized");
    }

    public void shutdown() {
        logger.debug("GamePacketHandler shutdown");
    }

    public void handleGamePacket(NetworkEngine.IncomingMessage message) {
        byte[] data = message.getData();
        if (data.length < 1) return;

        int packetId = data[0] & 0xFF;

        switch (packetId) {
            case 1: // Player position update
                handlePlayerPositionUpdate(data);
                break;
            case 2: // NPC update
                handleNPCUpdate(data);
                break;
            case 3: // Chat message
                handleChatMessage(data);
                break;
            case 4: // Inventory update
                handleInventoryUpdate(data);
                break;
            case 5: // Interface update
                handleInterfaceUpdate(data);
                break;
            default:
                logger.debug("Unknown game packet: {}", packetId);
        }
    }

    private void handlePlayerPositionUpdate(byte[] data) {
        if (data.length < 7) return;

        ByteBuffer buffer = ByteBuffer.wrap(data);
        buffer.get(); // Skip packet ID

        int x = buffer.getShort();
        int y = buffer.getShort();
        int plane = buffer.get();

        clientCore.getPlayerState().setPosition(x, y, plane);
        logger.debug("Player position updated: ({}, {}, {})", x, y, plane);
    }

    private void handleNPCUpdate(byte[] data) {
        if (data.length < 8) return;

        ByteBuffer buffer = ByteBuffer.wrap(data);
        buffer.get(); // Skip packet ID

        int npcIndex = buffer.getShort();
        int npcId = buffer.getShort();
        int x = buffer.getShort();
        int y = buffer.getShort();

        clientCore.getWorldState().addNPC(npcIndex, npcId, x, y, 0);
        logger.debug("NPC updated: index={}, id={}, pos=({}, {})", npcIndex, npcId, x, y);
    }

    private void handleChatMessage(byte[] data) {
        if (data.length < 4) return;

        ByteBuffer buffer = ByteBuffer.wrap(data);
        buffer.get(); // Skip packet ID

        int usernameLength = buffer.get() & 0xFF;
        byte[] usernameBytes = new byte[usernameLength];
        buffer.get(usernameBytes);
        String username = new String(usernameBytes, StandardCharsets.UTF_8);

        int messageLength = buffer.remaining();
        byte[] messageBytes = new byte[messageLength];
        buffer.get(messageBytes);
        String message = new String(messageBytes, StandardCharsets.UTF_8);

        clientCore.getEventSystem().fireChatMessage(username, message, 0);
        logger.debug("Chat message: {} says '{}'", username, message);
    }

    private void handleInventoryUpdate(byte[] data) {
        if (data.length < 4) return;

        ByteBuffer buffer = ByteBuffer.wrap(data);
        buffer.get(); // Skip packet ID

        int slot = buffer.get() & 0xFF;
        int itemId = buffer.getShort();
        int quantity = buffer.getInt();

        clientCore.getInventoryState().setInventoryItem(slot, itemId, quantity);
        logger.debug("Inventory updated: slot={}, item={}, qty={}", slot, itemId, quantity);
    }

    private void handleInterfaceUpdate(byte[] data) {
        if (data.length < 3) return;

        ByteBuffer buffer = ByteBuffer.wrap(data);
        buffer.get(); // Skip packet ID

        int interfaceId = buffer.getShort();

        clientCore.getInterfaceState().openInterface(interfaceId);
        logger.debug("Interface opened: {}", interfaceId);
    }

    public void sendGameAction(NetworkEngine.OutgoingMessage message) {
        logger.debug("Sending game action");
        // In real implementation, this would send over TCP socket
    }
}

/**
 * HeartbeatHandler - Manages connection heartbeat.
 */
class HeartbeatHandler {
    private static final Logger logger = LoggerFactory.getLogger(HeartbeatHandler.class);
    private static final long HEARTBEAT_INTERVAL = 30000; // 30 seconds

    private final NetworkEngine networkEngine;
    private long lastHeartbeatSent = 0;

    public HeartbeatHandler(NetworkEngine networkEngine) {
        this.networkEngine = networkEngine;
    }

    public void initialize() {
        logger.debug("HeartbeatHandler initialized");
    }

    public void shutdown() {
        logger.debug("HeartbeatHandler shutdown");
    }

    public void tick() {
        long now = System.currentTimeMillis();

        if (networkEngine.isConnected() && now - lastHeartbeatSent > HEARTBEAT_INTERVAL) {
            sendHeartbeat();
            lastHeartbeatSent = now;
        }
    }

    private void sendHeartbeat() {
        HeartbeatPacket packet = new HeartbeatPacket();
        NetworkEngine.OutgoingMessage message = new NetworkEngine.OutgoingMessage(
            NetworkEngine.MessageType.HEARTBEAT, packet.encode());
        networkEngine.queueOutgoingMessage(message);

        logger.debug("Heartbeat sent");
    }

    public void handleHeartbeat(NetworkEngine.IncomingMessage message) {
        logger.debug("Heartbeat received from server");
    }

    public void sendHeartbeat(NetworkEngine.OutgoingMessage message) {
        logger.debug("Sending heartbeat to server");
        // In real implementation, this would send over TCP socket
    }
}

/**
 * Packet classes for network communication.
 */

abstract class Packet {
    public abstract byte[] encode();
}

class LoginPacket extends Packet {
    private final String username;
    private final String password;
    private final String otp;

    public LoginPacket(String username, String password) {
        this(username, password, null);
    }
    
    public LoginPacket(String username, String password, String otp) {
        this.username = username != null ? username : "";
        this.password = password != null ? password : "";
        this.otp = otp != null && !otp.isEmpty() ? otp : "";
    }

    @Override
    public byte[] encode() {
        byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
        byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
        byte[] otpBytes = otp.getBytes(StandardCharsets.UTF_8);

        // Calculate total size: packet_id + username_len + username + password_len + password + otp_len + otp
        int totalSize = 1 + 1 + usernameBytes.length + 1 + passwordBytes.length + 1 + otpBytes.length;
        ByteBuffer buffer = ByteBuffer.allocate(totalSize);
        
        buffer.put((byte) 0); // Login packet ID
        buffer.put((byte) usernameBytes.length);
        buffer.put(usernameBytes);
        buffer.put((byte) passwordBytes.length);
        buffer.put(passwordBytes);
        buffer.put((byte) otpBytes.length);
        buffer.put(otpBytes);

        return buffer.array();
    }
    
    public String getUsername() { return username; }
    public String getPassword() { return password; }
    public String getOtp() { return otp; }
    public boolean hasOtp() { return !otp.isEmpty(); }
}

class ChatPacket extends Packet {
    private final String message;

    public ChatPacket(String message) {
        this.message = message != null ? message : "";
    }

    @Override
    public byte[] encode() {
        byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);

        ByteBuffer buffer = ByteBuffer.allocate(2 + messageBytes.length);
        buffer.put((byte) 10); // Chat packet ID
        buffer.put((byte) messageBytes.length);
        buffer.put(messageBytes);

        return buffer.array();
    }
}

class MovementPacket extends Packet {
    private final int x, y;
    private final boolean running;

    public MovementPacket(int x, int y, boolean running) {
        this.x = x;
        this.y = y;
        this.running = running;
    }

    @Override
    public byte[] encode() {
        ByteBuffer buffer = ByteBuffer.allocate(6);
        buffer.put((byte) 11); // Movement packet ID
        buffer.putShort((short) x);
        buffer.putShort((short) y);
        buffer.put(running ? (byte) 1 : (byte) 0);

        return buffer.array();
    }
}

class ObjectInteractionPacket extends Packet {
    private final int objectId, x, y, option;

    public ObjectInteractionPacket(int objectId, int x, int y, int option) {
        this.objectId = objectId;
        this.x = x;
        this.y = y;
        this.option = option;
    }

    @Override
    public byte[] encode() {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.put((byte) 12); // Object interaction packet ID
        buffer.putShort((short) objectId);
        buffer.putShort((short) x);
        buffer.putShort((short) y);
        buffer.put((byte) option);

        return buffer.array();
    }
}

class NPCInteractionPacket extends Packet {
    private final int npcIndex, option;

    public NPCInteractionPacket(int npcIndex, int option) {
        this.npcIndex = npcIndex;
        this.option = option;
    }

    @Override
    public byte[] encode() {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.put((byte) 13); // NPC interaction packet ID
        buffer.putShort((short) npcIndex);
        buffer.put((byte) option);

        return buffer.array();
    }
}

class LogoutPacket extends Packet {
    @Override
    public byte[] encode() {
        return new byte[]{(byte) 99}; // Logout packet ID
    }
}

class HeartbeatPacket extends Packet {
    @Override
    public byte[] encode() {
        ByteBuffer buffer = ByteBuffer.allocate(9);
        buffer.put((byte) 100); // Heartbeat packet ID
        buffer.putLong(System.currentTimeMillis());
        return buffer.array();
    }
}
