package pp.monopoly.game.server;

import pp.monopoly.MonopolyConfig;
import pp.monopoly.message.client.*;
import pp.monopoly.message.server.ServerMessage;
import pp.monopoly.message.server.TradeReply;
import pp.monopoly.message.server.TradeRequest;
import pp.monopoly.message.server.ViewAssetsResponse;
import pp.monopoly.model.fields.BoardManager;
import pp.monopoly.model.fields.PropertyField;

import java.lang.System.Logger;
import java.lang.System.Logger.Level;

import pp.monopoly.MonopolyConfig;
import pp.monopoly.message.client.ClientInterpreter;
import pp.monopoly.message.server.ServerMessage;

/**
 * Controls the server-side game logic for Monopoly.
 * Manages game states, player interactions, and message handling.
 */
public class ServerGameLogic implements ClientInterpreter {
    private static final Logger LOGGER = System.getLogger(ServerGameLogic.class.getName());

    private final MonopolyConfig config;
    private final PlayerHandler playerHandler = new PlayerHandler(this);
    private final ServerSender serverSender;
    private ServerState state = ServerState.CREATEGAME;
    private static final int MAX_PLAYERS = 6;
    private BoardManager boardManager = new BoardManager();

    /**
     * Constructs a ServerGameLogic instance with the specified sender and configuration.
     *
     * @param serverSender the sender used to send messages to clients
     * @param config       the game configuration
     */
    public ServerGameLogic(ServerSender serverSender, MonopolyConfig config) {
        this.serverSender = serverSender;
        this.config = config;
    }

    /**
     * Retrieves the current state of the game.
     *
     * @return the current ServerState
     */
    ServerState getState() {
        return state;
    }

    /**
     * Sets a new state for the game and logs the state transition.
     *
     * @param newState the new ServerState to transition to
     */
    void setState(ServerState newState) {
        LOGGER.log(Level.DEBUG, "State transition {0} --> {1}", state, newState);
        state = newState;
    }

    /**
     * Sends a message to a specified player.
     *
     * @param player the Player to whom the message is sent
     * @param msg    the ServerMessage to send
     */
    void send(Player player, ServerMessage msg) {
        if (player != null && msg != null) {
            serverSender.send(player.getId(), msg);
            LOGGER.log(Level.DEBUG, "Message sent to player {0}: {1}", player.getName(), msg.getClass().getSimpleName());
        } else {
            LOGGER.log(Level.WARNING, "Attempted to send a null message or to a null player");
        }
    }

    /**
     * Adds a new player to the game if the game is in the LOBBY state and the maximum
     * player limit has not been reached.
     *
     * @param player the Player to add to the game
     * @return the added Player, or null if the player could not be added
     */
    public Player addPlayer(Player player) {
        if (state != ServerState.LOBBY) {
            LOGGER.log(Level.WARNING, "Cannot add player; game is not in LOBBY state.");
            return null;
        }

        if (playerHandler.getPlayerCount() >= MAX_PLAYERS) {
            LOGGER.log(Level.WARNING, "Cannot add player; maximum player limit reached.");
            return null;
        }

        playerHandler.addPlayer(player);
        LOGGER.log(Level.DEBUG, "Player added: {0}", player.getId());

        return player;
    }

    /**
     * Adds a new player to the game if the game is in the LOBBY state and the maximum
     * player limit has not been reached.
     *
     * @param id the id of the player to add to the game
     * @return the added Player, or null if the player could not be added
     */
    public Player addPlayer(int id) {
        Player player = new Player(id, playerHandler);
        if (state != ServerState.LOBBY) {
            LOGGER.log(Level.WARNING, "Cannot add player; game is not in LOBBY state.");
            return null;
        }

        if (playerHandler.getPlayerCount() >= MAX_PLAYERS) {
            LOGGER.log(Level.WARNING, "Cannot add player; maximum player limit reached.");
            return null;
        }

        playerHandler.addPlayer(player);
        LOGGER.log(Level.DEBUG, "Player added: {0}", player.getId());

        return player;
    }

    /**
     * Handles a BuyPropertyRequest from a player, allowing the player to purchase a property
     * if it is unowned and they have sufficient funds.
     *
     * @param msg  the BuyPropertyRequest received from the player
     * @param from the connection ID of the player who sent the request
     */
    @Override
    public void received(BuyPropertyRequest msg, int from) {
        Player player = playerHandler.getPlayerById(from);
        if (player != null && state == ServerState.INGAME) {
            PropertyField property = (PropertyField) boardManager.getFieldAtIndex(player.move(0)); // Assuming player position for property
            
            if (property.getOwner() == null && player.getAccountBalance() >= property.getPrice()) {
                player.buyProperty(property);
                property.setOwner(player);
                player.earnMoney(-property.getPrice());
                LOGGER.log(Level.INFO, "Player {0} bought property {1}", player.getName(), property.getName());
            } else {
                LOGGER.log(Level.WARNING, "Player {0} cannot buy property {1}", player.getName(), property.getName());
            }
        }
    }

    /**
     * Handles an EndTurn request, ending the player's turn and advancing to the next player.
     *
     * @param msg  the EndTurn message received from the player
     * @param from the connection ID of the player who sent the request
     */
    @Override
    public void received(EndTurn msg, int from) {
        Player player = playerHandler.getPlayerById(from);
        if (player != null && state == ServerState.INGAME) {
            LOGGER.log(Level.DEBUG, "Ending turn for player {0}", player.getName());
            playerHandler.nextPlayer();
        }
    }

    /**
     * Handles a PlayerReady message, marking the player as ready.
     *
     * @param msg  the PlayerReady message received from the player
     * @param from the connection ID of the player who sent the request
     */
    @Override
    public void received(PlayerReady msg, int from) {
        Player player = playerHandler.getPlayerById(from);
        if (player != null) {
            player.setName(msg.getName());
            player.setColor(msg.getColor());
            player.setName(msg.getName());
            LOGGER.log(Level.DEBUG, "Player {0} is ready", player.getName());
        }
    }

    /**
     * Handles a RollDice message, rolling dice for the player and moving them on the board.
     *
     * @param msg  the RollDice message received from the player
     * @param from the connection ID of the player who sent the request
     */
    @Override
    public void received(RollDice msg, int from) {
        Player player = playerHandler.getPlayerById(from);
        if (player != null && state == ServerState.INGAME) {
            send(player, player.rollDice());
        }
    }

    /**
     * Handles a TradeOffer message by forwarding the trade offer to the receiving player.
     *
     * @param msg  the TradeOffer message received from the initiating player
     * @param from the connection ID of the player who sent the offer
     */
    @Override
    public void received(TradeOffer msg, int from) {
        Player sender = playerHandler.getPlayerById(from);
        Player receiver = playerHandler.getPlayerById(msg.getReceiverId());
        
        if (sender != null && receiver != null) {
            LOGGER.log(Level.INFO, "Player {0} offers a trade to player {1}", sender.getName(), receiver.getName());
            send(playerHandler.getPlayerById(msg.getReceiverId()), new TradeRequest(msg.getReceiverId(), msg.getTradeHandler()));
        }
    }

    /**
     * Handles a TradeResponse message by forwarding the response back to the initiating player.
     *
     * @param msg  the TradeResponse message received from the receiving player
     * @param from the connection ID of the player who sent the response
     */
    @Override
    public void received(TradeResponse msg, int from) {
        Player responder = playerHandler.getPlayerById(from);
        Player initiator = playerHandler.getPlayerById(msg.getInitiatorId());
        
        if (responder != null && initiator != null) {
            LOGGER.log(Level.INFO, "Player {0} responded to trade with player {1}", responder.getName(), initiator.getName());
            send(initiator, new TradeReply(msg.getInitiatorId(), msg.getTradeHandler()));
        }
    }

    /**
     * Handles a ViewAssetsRequest message, sending the player a response containing their assets.
     *
     * @param msg  the ViewAssetsRequest message received from the player
     * @param from the connection ID of the player who sent the request
     */
    @Override
    public void received(ViewAssetsRequest msg, int from) {
        Player player = playerHandler.getPlayerById(from);
        if (player != null) {
            LOGGER.log(Level.DEBUG, "Processing ViewAssetsRequest for player {0}", player.getName());
            
            send(player, new ViewAssetsResponse(player.getProperties(), player.getAccountBalance(), player.getNumJailCard()));
        }
    }

    /**
     * Retrieves the board manager, which manages the game board.
     *
     * @return the BoardManager instance managing the game board
     */
    public BoardManager getBoardManager() {
        return boardManager;
    }

    public Player getPlayerById(int id) {
        return playerHandler.getPlayerById(id);
    }
}
