package com.openosrs.client.engine;

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

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * NetworkEngine - Handles all network communication with RuneScape servers.
 * 
 * This engine manages:
 * - Connection to game servers
 * - Packet encoding/decoding
 * - Message queuing and processing
 * - Connection state management
 * - Heartbeat and keepalive
 * 
 * The network protocol is designed to be compatible with OSRS while providing
 * clean interfaces for agents to monitor and interact with network traffic.
 */
public class NetworkEngine {
    private static final Logger logger = LoggerFactory.getLogger(NetworkEngine.class);
    
    private final ClientCore clientCore;
    private final AtomicBoolean initialized = new AtomicBoolean(false);
    private final AtomicBoolean connected = new AtomicBoolean(false);
    
    // Message queues
    private final BlockingQueue<IncomingMessage> incomingMessages = new LinkedBlockingQueue<>();
    private final BlockingQueue<OutgoingMessage> outgoingMessages = new LinkedBlockingQueue<>();
    
    // Connection management
    private final AtomicInteger connectionAttempts = new AtomicInteger(0);
    private final AtomicLong lastHeartbeat = new AtomicLong(0);
    private final AtomicLong bytesReceived = new AtomicLong(0);
    private final AtomicLong bytesSent = new AtomicLong(0);
    
    // Protocol handlers
    private final LoginHandler loginHandler;
    private final GamePacketHandler gamePacketHandler;
    private final HeartbeatHandler heartbeatHandler;
    
    public NetworkEngine(ClientCore clientCore) {
        this.clientCore = clientCore;
        
        // Initialize protocol handlers
        this.loginHandler = new LoginHandler(this);
        this.gamePacketHandler = new GamePacketHandler(this, clientCore);
        this.heartbeatHandler = new HeartbeatHandler(this);
        
        logger.debug("NetworkEngine created");
    }
    
    public void initialize() {
        if (initialized.get()) {
            logger.warn("NetworkEngine already initialized");
            return;
        }
        
        logger.info("Initializing NetworkEngine");
        
        try {
            // Initialize handlers
            loginHandler.initialize();
            gamePacketHandler.initialize();
            heartbeatHandler.initialize();
            
            initialized.set(true);
            logger.info("NetworkEngine initialized");
            
        } catch (Exception e) {
            logger.error("Failed to initialize NetworkEngine", e);
            throw new RuntimeException("NetworkEngine initialization failed", e);
        }
    }
    
    public void shutdown() {
        if (!initialized.get()) {
            return;
        }
        
        logger.info("Shutting down NetworkEngine");
        
        try {
            // Disconnect if connected
            if (connected.get()) {
                disconnect();
            }
            
            // Shutdown handlers
            heartbeatHandler.shutdown();
            gamePacketHandler.shutdown();
            loginHandler.shutdown();
            
            // Clear message queues
            incomingMessages.clear();
            outgoingMessages.clear();
            
            initialized.set(false);
            logger.info("NetworkEngine shutdown complete");
            
        } catch (Exception e) {
            logger.error("Error during NetworkEngine shutdown", e);
        }
    }
    
    /**
     * Process incoming and outgoing messages (called each game tick).
     */
    public void processMessages() {
        if (!initialized.get()) {
            return;
        }
        
        try {
            // Process incoming messages
            int processedIncoming = 0;
            IncomingMessage inMsg;
            while ((inMsg = incomingMessages.poll()) != null && processedIncoming < 50) {
                processIncomingMessage(inMsg);
                processedIncoming++;
            }
            
            // Process outgoing messages
            int processedOutgoing = 0;
            OutgoingMessage outMsg;
            while ((outMsg = outgoingMessages.poll()) != null && processedOutgoing < 50) {
                processOutgoingMessage(outMsg);
                processedOutgoing++;
            }
            
            // Update heartbeat
            heartbeatHandler.tick();
            
        } catch (Exception e) {
            logger.error("Error processing network messages", e);
        }
    }
    
    private void processIncomingMessage(IncomingMessage message) {
        try {
            bytesReceived.addAndGet(message.getData().length);
            
            switch (message.getType()) {
                case LOGIN_RESPONSE:
                    loginHandler.handleLoginResponse(message);
                    break;
                    
                case GAME_PACKET:
                    gamePacketHandler.handleGamePacket(message);
                    break;
                    
                case HEARTBEAT:
                    heartbeatHandler.handleHeartbeat(message);
                    break;
                    
                default:
                    logger.warn("Unknown incoming message type: {}", message.getType());
            }
            
        } catch (Exception e) {
            logger.error("Error processing incoming message: {}", message.getType(), e);
        }
    }
    
    private void processOutgoingMessage(OutgoingMessage message) {
        try {
            bytesSent.addAndGet(message.getData().length);
            
            // Send message through appropriate channel
            switch (message.getType()) {
                case LOGIN_REQUEST:
                    loginHandler.sendLoginRequest(message);
                    break;
                    
                case GAME_ACTION:
                    gamePacketHandler.sendGameAction(message);
                    break;
                    
                case HEARTBEAT:
                    heartbeatHandler.sendHeartbeat(message);
                    break;
                    
                default:
                    logger.warn("Unknown outgoing message type: {}", message.getType());
            }
            
        } catch (Exception e) {
            logger.error("Error processing outgoing message: {}", message.getType(), e);
        }
    }
    
    /**
     * Connect to the game server.
     */
    public void connect(String worldUrl, int port) {
        if (connected.get()) {
            logger.warn("Already connected to server");
            return;
        }
        
        logger.info("Connecting to {}:{}", worldUrl, port);
        connectionAttempts.incrementAndGet();
        
        try {
            // Update network state
            clientCore.getNetworkState().setConnectionState(
                clientCore.getNetworkState().ConnectionState.CONNECTING);
            
            // Simulate connection process
            // In real implementation, this would establish TCP/WebSocket connection
            Thread.sleep(1000); // Simulate connection time
            
            connected.set(true);
            clientCore.getNetworkState().setConnectionState(
                clientCore.getNetworkState().ConnectionState.CONNECTED);
            
            logger.info("Connected to game server");
            
        } catch (Exception e) {
            logger.error("Failed to connect to server", e);
            clientCore.getNetworkState().setConnectionState(
                clientCore.getNetworkState().ConnectionState.FAILED);
        }
    }
    
    /**
     * Disconnect from the game server.
     */
    public void disconnect() {
        if (!connected.get()) {
            logger.warn("Not connected to server");
            return;
        }
        
        logger.info("Disconnecting from server");
        
        try {
            // Send logout packet
            sendLogoutRequest();
            
            connected.set(false);
            clientCore.getNetworkState().setConnectionState(
                clientCore.getNetworkState().ConnectionState.DISCONNECTED);
            
            logger.info("Disconnected from server");
            
        } catch (Exception e) {
            logger.error("Error during disconnect", e);
        }
    }
    
    /**
     * Queue an outgoing message to be sent.
     */
    public void queueOutgoingMessage(OutgoingMessage message) {
        if (!connected.get() && message.getType() != MessageType.LOGIN_REQUEST) {
            logger.warn("Cannot send message - not connected: {}", message.getType());
            return;
        }
        
        if (!outgoingMessages.offer(message)) {
            logger.warn("Outgoing message queue full, dropping message: {}", message.getType());
        }
    }
    
    /**
     * Queue an incoming message for processing.
     */
    public void queueIncomingMessage(IncomingMessage message) {
        if (!incomingMessages.offer(message)) {
            logger.warn("Incoming message queue full, dropping message: {}", message.getType());
        }
    }
    
    // Connection status
    public boolean isConnected() { return connected.get(); }
    public boolean isInitialized() { return initialized.get(); }
    public int getConnectionAttempts() { return connectionAttempts.get(); }
    
    // Statistics
    public long getBytesReceived() { return bytesReceived.get(); }
    public long getBytesSent() { return bytesSent.get(); }
    public int getIncomingQueueSize() { return incomingMessages.size(); }
    public int getOutgoingQueueSize() { return outgoingMessages.size(); }
    
    // Convenience methods for common actions
    public void sendChatMessage(String message) {
        if (message == null || message.trim().isEmpty()) return;
        
        ChatPacket packet = new ChatPacket(message.trim());
        queueOutgoingMessage(new OutgoingMessage(MessageType.GAME_ACTION, packet.encode()));
    }
    
    public void sendMovement(int x, int y, boolean running) {
        MovementPacket packet = new MovementPacket(x, y, running);
        queueOutgoingMessage(new OutgoingMessage(MessageType.GAME_ACTION, packet.encode()));
    }
    
    public void sendObjectInteraction(int objectId, int x, int y, int option) {
        ObjectInteractionPacket packet = new ObjectInteractionPacket(objectId, x, y, option);
        queueOutgoingMessage(new OutgoingMessage(MessageType.GAME_ACTION, packet.encode()));
    }
    
    public void sendNPCInteraction(int npcIndex, int option) {
        NPCInteractionPacket packet = new NPCInteractionPacket(npcIndex, option);
        queueOutgoingMessage(new OutgoingMessage(MessageType.GAME_ACTION, packet.encode()));
    }
    
    public void sendLoginRequest(String username, String password) {
        LoginPacket packet = new LoginPacket(username, password);
        queueOutgoingMessage(new OutgoingMessage(MessageType.LOGIN_REQUEST, packet.encode()));
    }
    
    private void sendLogoutRequest() {
        LogoutPacket packet = new LogoutPacket();
        queueOutgoingMessage(new OutgoingMessage(MessageType.GAME_ACTION, packet.encode()));
    }
    
    /**
     * Message types for network communication.
     */
    public enum MessageType {
        LOGIN_REQUEST,
        LOGIN_RESPONSE,
        GAME_PACKET,
        GAME_ACTION,
        HEARTBEAT,
        LOGOUT
    }
    
    /**
     * Base class for network messages.
     */
    public abstract static class NetworkMessage {
        private final MessageType type;
        private final byte[] data;
        private final long timestamp;
        
        protected NetworkMessage(MessageType type, byte[] data) {
            this.type = type;
            this.data = data != null ? data : new byte[0];
            this.timestamp = System.currentTimeMillis();
        }
        
        public MessageType getType() { return type; }
        public byte[] getData() { return data; }
        public long getTimestamp() { return timestamp; }
    }
    
    /**
     * Incoming message from server.
     */
    public static class IncomingMessage extends NetworkMessage {
        public IncomingMessage(MessageType type, byte[] data) {
            super(type, data);
        }
    }
    
    /**
     * Outgoing message to server.
     */
    public static class OutgoingMessage extends NetworkMessage {
        public OutgoingMessage(MessageType type, byte[] data) {
            super(type, data);
        }
    }
}