package com.zombie.game.server;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.zombie.game.ecs.ECSWorld;
import com.zombie.game.model.*;
import com.zombie.game.model.Constants;
import com.zombie.game.systems.StateSyncSystem;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Main WebSocket server handling all client connections / 主WebSocket服务器，处理所有客户端连接
 * Manages game rooms, player connections, and message routing / 管理游戏房间、玩家连接和消息路由
 */
public class GameWebSocketServer extends WebSocketServer {
    // SLF4J logger for server events / SLF4J日志记录器，用于服务器事件
    private static final Logger logger = LoggerFactory.getLogger(GameWebSocketServer.class);
    // JSON serializer/deserializer / JSON序列化/反序列化工具
    private Gson gson;
    // Room manager for creating and tracking game rooms / 房间管理器，用于创建和追踪游戏房间
    private RoomManager roomManager;
    // Game service for game logic / 游戏服务，用于游戏逻辑处理
    private GameService gameService;
    // Map connection to player ID / 连接映射到玩家ID
    private Map<WebSocket, String> connectionToPlayer;
    // Map player ID to connection / 玩家ID映射到连接
    private Map<String, WebSocket> playerToConnection;
    // Timer for broadcasting room list periodically / 定时器，用于定期广播房间列表
    private Timer roomListTimer;

    // Constructor: initializes server with given port / 构造函数：用指定端口初始化服务器
    public GameWebSocketServer(int port) {
        super(new InetSocketAddress(port));
        this.gson = new Gson();
        this.roomManager = new RoomManager();
        this.connectionToPlayer = new ConcurrentHashMap<>();
        this.playerToConnection = new ConcurrentHashMap<>();
        this.gameService = new GameService(this::broadcastGameState);
    }

    // Called when a new client connects / 新客户端连接时调用
    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        logger.info("New connection: {}", conn.getRemoteSocketAddress());
    }

    // Called when a client disconnects / 客户端断开连接时调用
    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        logger.info("Connection closed: {}", conn.getRemoteSocketAddress());
        // Remove player mapping and handle room leave / 移除玩家映射并处理离开房间
        String playerId = connectionToPlayer.remove(conn);
        if (playerId != null) {
            playerToConnection.remove(playerId);
            handleLeaveRoomByPlayerId(playerId);
        }
    }

    // Called when a message is received from a client / 收到客户端消息时调用
    @Override
    public void onMessage(WebSocket conn, String message) {
        try {
            // Parse message type and data / 解析消息类型和数据
            JsonObject msg = gson.fromJson(message, JsonObject.class);
            String type = msg.get("type").getAsString();
            JsonObject data = msg.has("data") ? msg.getAsJsonObject("data") : new JsonObject();

            // Route message to appropriate handler / 将消息路由到对应的处理函数
            switch (type) {
                case Constants.MSG_CREATE_ROOM:
                    handleCreateRoom(conn, data);
                    break;
                case Constants.MSG_JOIN_ROOM:
                    handleJoinRoom(conn, data);
                    break;
                case Constants.MSG_LEAVE_ROOM:
                    handleLeaveRoomByConn(conn);
                    break;
                case Constants.MSG_ROOM_LIST:
                    handleRoomList(conn);
                    break;
                case Constants.MSG_READY:
                    handleReady(conn);
                    break;
                case Constants.MSG_START_GAME:
                    handleStartGame(conn);
                    break;
                case Constants.MSG_PLAYER_INPUT:
                    handlePlayerInput(conn, data);
                    break;
            }
        } catch (Exception e) {
            logger.error("Error processing message from {}", conn.getRemoteSocketAddress(), e);
            sendError(conn, "Invalid message format");
        }
    }

    // Called when a WebSocket error occurs / WebSocket错误发生时调用
    @Override
    public void onError(WebSocket conn, Exception ex) {
        logger.error("WebSocket error on {}", conn != null ? conn.getRemoteSocketAddress() : "null", ex);
    }

    // Called when the server starts successfully / 服务器成功启动时调用
    @Override
    public void onStart() {
        logger.info("Game WebSocket Server started on port {}", getPort());
        // Start periodic room list broadcast (every 2 seconds) / 启动定期房间列表广播（每2秒）
        roomListTimer = new Timer(true);
        roomListTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                broadcastRoomList();
            }
        }, 0, 2000);
    }

    // Handle create room request / 处理创建房间请求
    private void handleCreateRoom(WebSocket conn, JsonObject data) {
        // Validate required fields / 验证必需字段
        if (!MessageUtils.hasRequired(data, "playerName")) {
            sendError(conn, "Missing playerName");
            return;
        }
        String playerName = MessageUtils.getString(data, "playerName");
        // Generate unique IDs for player and room / 为玩家和房间生成唯一ID
        String playerId = UUID.randomUUID().toString();
        String roomId = UUID.randomUUID().toString().substring(0, 8);

        // Track connection mappings / 记录连接映射
        connectionToPlayer.put(conn, playerId);
        playerToConnection.put(playerId, conn);

        // Create new room with player as host / 创建新房间，玩家作为房主
        Room room = new Room(roomId, playerId, playerName);
        roomManager.addRoom(roomId, room);

        // Send room state back to creator / 向创建者发送房间状态
        sendToConnection(conn, Constants.MSG_ROOM_STATE, room.toStateMap(playerId));
        logger.info("Room created: {} by {}", roomId, playerName);
    }

    // Handle join room request / 处理加入房间请求
    private void handleJoinRoom(WebSocket conn, JsonObject data) {
        // Validate required fields / 验证必需字段
        if (!MessageUtils.hasRequired(data, "roomId", "playerName")) {
            sendError(conn, "Missing roomId or playerName");
            return;
        }
        String roomId = MessageUtils.getString(data, "roomId");
        String playerName = MessageUtils.getString(data, "playerName");

        Room room = roomManager.getRoom(roomId);
        if (room == null) {
            sendError(conn, "Room not found");
            return;
        }

        // Check if room is full (max 4 players) / 检查房间是否已满（最多4人）
        if (room.getPlayerCount() >= 4) {
            sendError(conn, "Room is full");
            return;
        }

        // Generate player ID and track connection / 生成玩家ID并追踪连接
        String playerId = UUID.randomUUID().toString();
        connectionToPlayer.put(conn, playerId);
        playerToConnection.put(playerId, conn);

        // Add player to room / 将玩家添加到房间
        room.addPlayer(playerId, playerName);
        roomManager.joinRoom(roomId, playerId, playerName);

        // Notify all players of updated room state / 通知所有玩家房间状态已更新
        broadcastRoomState(room);
        logger.info("Player {} joined room {}", playerName, roomId);
    }

    // Handle leave room by connection / 通过连接处理离开房间
    private void handleLeaveRoomByConn(WebSocket conn) {
        String playerId = connectionToPlayer.get(conn);
        if (playerId == null) return;
        handleLeaveRoomByPlayerId(playerId);
    }

    // Handle leave room by player ID / 通过玩家ID处理离开房间
    private void handleLeaveRoomByPlayerId(String playerId) {
        Room room = roomManager.leaveRoom(playerId);
        if (room == null) return;

        Room updatedRoom = roomManager.getRoom(room.getId());
        if (updatedRoom == null) {
            // Room was deleted, stop the game / 房间被删除，停止游戏
            gameService.stopGame(room.getId());
        } else {
            // Notify remaining players / 通知剩余玩家
            broadcastRoomState(updatedRoom);
        }
    }

    // Handle room list request / 处理房间列表请求
    private void handleRoomList(WebSocket conn) {
        List<Map<String, Object>> roomList = new ArrayList<>();
        for (Room room : roomManager.getAvailableRooms()) {
            roomList.add(room.toRoomListMap());
        }
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("rooms", roomList);
        sendToConnection(conn, Constants.MSG_ROOM_LIST, data);
    }

    // Handle player ready toggle / 处理玩家准备状态切换
    private void handleReady(WebSocket conn) {
        String playerId = connectionToPlayer.get(conn);
        if (playerId == null) return;

        Room room = roomManager.getRoomByPlayerId(playerId);
        if (room == null) return;

        PlayerInfo player = room.getPlayer(playerId);
        if (player != null) {
            // Toggle ready state / 切换准备状态
            player.setReady(!player.isReady());
            broadcastRoomState(room);
        }
    }

    // Handle start game request (host only) / 处理开始游戏请求（仅房主）
    private void handleStartGame(WebSocket conn) {
        String playerId = connectionToPlayer.get(conn);
        if (playerId == null) {
            sendError(conn, "Not in a room / 未在房间中");
            return;
        }

        Room room = roomManager.getRoomByPlayerId(playerId);
        if (room == null) {
            sendError(conn, "Room not found / 房间不存在");
            return;
        }

        if (!room.isHost(playerId)) {
            sendError(conn, "Only host can start game / 只有房主可以开始游戏");
            return;
        }
        if (room.isGameStarted()) {
            sendError(conn, "Game already started / 游戏已经开始");
            return;
        }
        if (!room.allReady()) {
            sendError(conn, "Not all players are ready / 不是所有玩家都已准备");
            return;
        }

        // Set game started flag after startGame succeeds / startGame 成功后再设置标志
        try {
            startGame(room);
            room.setGameStarted(true);
        } catch (Exception e) {
            logger.error("Failed to start game for room {}", room.getId(), e);
            sendError(conn, "Failed to start game: " + e.getMessage());
        }
    }

    // Start the game for a room / 为房间启动游戏
    private void startGame(Room room) {
        // Initialize game world and create player entities / 初始化游戏世界并创建玩家实体
        Map<String, Map<String, Object>> playerInitData = gameService.startGame(room);

        // Send initial game data to each player / 向每位玩家发送初始游戏数据
        for (Map.Entry<String, Map<String, Object>> entry : playerInitData.entrySet()) {
            WebSocket pConn = playerToConnection.get(entry.getKey());
            if (pConn != null) {
                sendToConnection(pConn, Constants.MSG_GAME_STARTED, entry.getValue());
            }
        }

        // Start game loop after game_started is sent / game_started 发送完毕后再启动游戏循环
        gameService.startGameLoop(room.getId());
    }

    // Handle player input (movement, aim) / 处理玩家输入（移动、瞄准）
    private void handlePlayerInput(WebSocket conn, JsonObject data) {
        String playerId = connectionToPlayer.get(conn);
        if (playerId == null) return;

        Room room = roomManager.getRoomByPlayerId(playerId);
        if (room == null || !room.isGameStarted()) return;

        // Extract input data / 提取输入数据
        float dx = MessageUtils.getFloat(data, "dx", 0);
        float dy = MessageUtils.getFloat(data, "dy", 0);
        float aimX = MessageUtils.getFloat(data, "aimX", 0);
        float aimY = MessageUtils.getFloat(data, "aimY", 0);
        int seq = MessageUtils.getInt(data, "seq", 0);

        gameService.processPlayerInput(room.getId(), playerId, dx, dy, aimX, aimY, seq);
    }

    // Broadcast game state to all players in a room / 向房间内所有玩家广播游戏状态
    private void broadcastGameState(String roomId, ECSWorld world) {
        Room room = roomManager.getRoom(roomId);
        if (room == null) return;

        StateSyncSystem syncSystem = new StateSyncSystem();

        // Sync state under world lock / 在世界锁下同步状态
        synchronized (world.getLock()) {
            Map<String, Object> state = syncSystem.buildGameState(world);
            for (PlayerInfo playerInfo : room.getPlayers()) {
                WebSocket pConn = playerToConnection.get(playerInfo.getId());
                if (pConn != null && pConn.isOpen()) {
                    sendToConnection(pConn, Constants.MSG_GAME_STATE, state);
                }
            }
        }
    }

    // Broadcast room state to all players in a room / 向房间内所有玩家广播房间状态
    private void broadcastRoomState(Room room) {
        for (PlayerInfo player : room.getPlayers()) {
            WebSocket pConn = playerToConnection.get(player.getId());
            if (pConn != null && pConn.isOpen()) {
                sendToConnection(pConn, Constants.MSG_ROOM_STATE, room.toStateMap(player.getId()));
            }
        }
    }

    // Broadcast room list to all unconnected clients / 向所有未连接的玩家广播房间列表
    private void broadcastRoomList() {
        List<Map<String, Object>> roomList = new ArrayList<>();
        for (Room room : roomManager.getAvailableRooms()) {
            roomList.add(room.toRoomListMap());
        }
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("rooms", roomList);

        // Only send to clients not in a room / 只发送给不在房间中的客户端
        for (WebSocket conn : getConnections()) {
            if (connectionToPlayer.get(conn) == null) {
                sendToConnection(conn, Constants.MSG_ROOM_LIST, data);
            }
        }
    }

    // Send a message to a specific connection / 向指定连接发送消息
    private void sendToConnection(WebSocket conn, String type, Object data) {
        if (conn != null && conn.isOpen()) {
            Map<String, Object> msg = new LinkedHashMap<>();
            msg.put("type", type);
            msg.put("data", data);
            conn.send(gson.toJson(msg));
        }
    }

    // Send error message to a connection / 向连接发送错误消息
    private void sendError(WebSocket conn, String message) {
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("message", message);
        sendToConnection(conn, Constants.MSG_ERROR, data);
    }
}
