package eu.mhsl.minenet.minigames.instance.room;

import eu.mhsl.minenet.minigames.Resource;
import eu.mhsl.minenet.minigames.instance.MineNetInstance;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.score.tournament.Tournament;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.util.MoveInstance;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.hub.Hub;
import eu.mhsl.minenet.minigames.instance.room.entity.GameSelector;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.attribute.Attribute;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.instance.anvil.AnvilLoader;

import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class Room extends MineNetInstance implements Spawnable {
    private static final Set<Room> rooms = new HashSet<>();
    private static final Map<Player, Room> players = new WeakHashMap<>();
    private static final Logger logger = Logger.getLogger("room");

    public static Room createRoom(Player owner) {
        logger.info("Creating room with owner " + owner.getUsername());
        setRoom(owner, new Room(owner));
        return getRoom(owner).orElseThrow();
    }

    public static Room createOwnerlessRoom() {
        Room room = new Room();
        rooms.add(room);
        return room;
    }

    public static void deleteRoom(Room room) {
        logger.info("Deleting room " + room.uuid);
        rooms.remove(room);
        players.values().removeAll(Collections.singleton(room));
        room.getAllMembers().forEach(player -> MoveInstance.move(player, Hub.INSTANCE));
        MoveInstance.forceCloseInstance(room);
    }

    public static Optional<Room> getRoom(Player p) {
        return Optional.ofNullable(players.get(p));
    }

    public static Optional<Room> getRoom(UUID uuid) {
        return rooms.stream().filter(room -> room.uuid.equals(uuid)).findFirst();
    }

    public static void setOwnRoom(Player p) {
        setRoom(p, getRoom(p).orElseThrow());
    }

    public static void setRoom(Player p, Room room) {
        p.clearEffects();
        p.clearTitle();
        p.getInventory().clear();
        p.setGameMode(GameMode.ADVENTURE);
        p.setInvisible(false);
        p.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).setBaseValue(p.getAttribute(Attribute.BLOCK_INTERACTION_RANGE).attribute().defaultValue());
        p.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(p.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).attribute().defaultValue());
        p.heal();
        p.setExp(0);
        p.setLevel(0);
        rooms.add(room);
        players.put(p, room);
        MoveInstance.move(p, room);
    }

    public static void unsetRoom(Player p) {
        logger.info("Unset room for " + p.getUsername());
        players.remove(p);
    }

    public static Set<Room> getAllRooms() {
        return rooms;
    }

    private Player owner;
    public final UUID uuid = UUID.randomUUID();
    public final boolean apiDriven;
    private GameSelector gameSelector;
    private final Tournament tournament = new Tournament();
    private Room(Player owner) {
        super(Dimension.THE_END.key);
        this.apiDriven = false;
        construct();
        setOwner(owner);
    }

    protected Room() {
        super(Dimension.THE_END.key);
        this.apiDriven = true;
        construct();
    }

    private void construct() {
        MinecraftServer.getInstanceManager().registerInstance(this);
        setChunkLoader(new AnvilLoader(Resource.LOBBY_MAP.getPath()));

        this.gameSelector = new GameSelector();
        this.gameSelector.setInstance(this, new Pos(0.5, 50, 19.5));

        eventNode().addListener(PlayerBlockBreakEvent.class, CommonEventHandles::cancel);
        eventNode().addListener(PlayerDisconnectEvent.class, playerDisconnectEvent -> unsetRoom(playerDisconnectEvent.getPlayer()));
    }

    public Player getOwner() {
        return owner;
    }

    public void setOwner(Player newOwner) {
        this.owner = newOwner;

        this.owner.eventNode().addListener(PlayerDisconnectEvent.class, playerDisconnectEvent -> {
            if(this.apiDriven) return; // prevent giving ownership on apiDriven rooms

            Player p = playerDisconnectEvent.getPlayer();

            if(p != this.owner) return;

            getAllMembers().stream()
                    .filter(player -> player != p) // exclude the current leaving owner
                    .findFirst()
                    .ifPresentOrElse(
                            this::setOwner,
                            () -> Room.getRoom(p).ifPresent(Room::deleteRoom) // delete room if no players are left
                    );

            Room.unsetRoom(p);

            new ChatMessage(Icon.ERROR).appendStatic("The room leader left!").send(getAllMembers());
            new ChatMessage(Icon.SCIENCE).appendStatic(this.owner.getUsername()).appendStatic(" is the new Leader!").send(getAllMembers().stream().filter(player -> player != this.owner).collect(Collectors.toSet()));
            new ChatMessage(Icon.SUCCESS).appendStatic("You are now the leader.").send(this.owner);
        });
    }

    public void moveMembersToInstance(Spawnable game) {
        this.getAllMembers().forEach(player -> MoveInstance.move(player, game));
    }

    public void moveMembersToRoomLobby() {
        this.getAllMembers().forEach(player -> MoveInstance.move(player, this));
    }

    public Set<Player> getAllMembers() {
        return players.keySet().stream()
                .filter(player -> getRoom(player).orElse(null) == this)
                .collect(Collectors.toSet());
    }

    public Tournament getTournament() {
        return this.tournament;
    }

    @Override
    public Pos getSpawn() {
        return new Pos(0.5, 50, 0.5);
    }
}
