package eu.mhsl.craftattack.teamLobby;

import eu.mhsl.craftattack.teamLobby.data.Team;
import eu.mhsl.craftattack.teamLobby.util.*;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockInteractEvent;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.anvil.AnvilLoader;
import net.minestom.server.instance.block.Block;
import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.potion.Potion;
import net.minestom.server.potion.PotionEffect;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
import org.spongepowered.configurate.ConfigurationNode;

import java.nio.file.Path;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public class Lobby extends InstanceContainer {
    private static final DynamicRegistry.Key<DimensionType> dimension = MinecraftServer.getDimensionTypeRegistry()
        .register(
            Key.key("mhsl:team_lobby"),
            DimensionType.builder()
                .ambientLight(2.0f)
                .build()
        );

    private final ConfigurationNode lobbyConfig = Main.getConfig().node("lobby");
    private final Pos buttonLocation = PosSerializer.deserialize(this.lobbyConfig.node("buttonLocation").getString());
    private final String targetServer = this.lobbyConfig.node("connectsTo").getString();
    public final Pos spawnPoint = PosSerializer.deserialize(this.lobbyConfig.node("spawnpoint").getString());

    public final Team team;
    private boolean isComplete;
    private boolean isJoining;

    public final ParticlePacket availableParticles = new ParticlePacket(
        Particle.SHRIEK,
        this.buttonLocation.add(0.5, 0, 0.5),
        new Vec(0, 0, 0),
        0.001f,
        1
    );

    public final ParticlePacket progressParticles = new ParticlePacket(
        Particle.TOTEM_OF_UNDYING,
        this.buttonLocation.add(0.5),
        new Vec(0.5, 0.5, 0.5),
        0.1f,
        5
    );

    public Lobby(Team team) {
        super(UUID.randomUUID(), dimension);
        MinecraftServer.getInstanceManager().registerInstance(this);
        this.team = team;

        this.eventNode()
            .addListener(PlayerBlockBreakEvent.class, CommonHandler::cancel)
            .addListener(AddEntityToInstanceEvent.class, this::onPlayerChange)
            .addListener(RemoveEntityFromInstanceEvent.class, this::onPlayerChange)
            .addListener(PlayerBlockInteractEvent.class, this::onBlockInteract);

        MinecraftServer.getSchedulerManager().scheduleTask(
            this::particleTick,
            TaskSchedule.seconds(3),
            TaskSchedule.millis(100)
        );

        Path worldPath = Path.of(ResourceExtractor.commonRessourcePath.toString(), "world");
        ResourceExtractor.extractResourceFolder("/world", worldPath);
        this.setChunkLoader(new AnvilLoader(worldPath));
    }

    private <TEvent> void onPlayerChange(TEvent event) {
        MinecraftServer.getSchedulerManager().scheduleNextTick(() -> {
            this.isJoining = false;
            boolean wasComplete = this.isComplete;
            var livingPlayers = this.team.users().stream()
                .filter(user -> !user.dead())
                .map(Team.User::uuid).toList();
            this.isComplete = this.getPlayers().stream()
                .map(Entity::getUuid)
                .collect(Collectors.toSet())
                .containsAll(livingPlayers);

            if(this.isComplete && !wasComplete) {
                this.playSound(Sound.sound(SoundEvent.ENTITY_EXPERIENCE_ORB_PICKUP, Sound.Source.PLAYER, 1f, 1f));
                this.everyMember(p -> p.sendMessage(Component.text(
                    "Das Team ist vollständig. Der Server kann jederzeit über den Knopf betreten werden!",
                    NamedTextColor.GREEN
                )));
            } else if(!this.isComplete && wasComplete) {
                this.playSound(Sound.sound(SoundEvent.ENTITY_EXPERIENCE_ORB_PICKUP, Sound.Source.PLAYER, 1f, 0.1f));
                this.everyMember(p -> p.sendMessage(Component.text(
                    "Das Team ist nicht mehr vollständig.",
                    NamedTextColor.RED
                )));
            }

            this.update();
        });
    }

    private synchronized void onBlockInteract(PlayerBlockInteractEvent event) {
        if(!event.getBlockPosition().sameBlock(this.buttonLocation)) return;
        if(this.isJoining) return;

        LocalTime now = LocalTime.now(ZoneId.of("Europe/Berlin"));
        LocalTime start = LocalTime.of(17, 0); // TODO 17 uhr
        LocalTime end = LocalTime.of(21, 0); // TODO 21 uhr

        if (now.isBefore(start) || now.isAfter(end)) {
            event.getPlayer().sendActionBar(Component.text("Der Beitritt ist nur zwischen 17 und 21 Uhr möglich.", NamedTextColor.RED));
            event.getPlayer().playSound(Sound.sound(SoundEvent.BLOCK_NOTE_BLOCK_BASS, Sound.Source.PLAYER, 1f, 0.5f));
            return;
        }

        if(!this.isComplete) {
            event.getPlayer().playSound(Sound.sound(SoundEvent.UI_BUTTON_CLICK, Sound.Source.PLAYER, 1f, 1f));
            this.everyMember(p -> p.sendActionBar(Component.text("Dein Team ist nicht vollständig!", NamedTextColor.RED)));
            return;
        }

        this.isJoining = true;
        this.connect();
        this.update();
    }

    private void update() {
        Block button = (this.isComplete ? Block.WARPED_BUTTON : Block.CRIMSON_BUTTON)
            .withProperty("face", "floor")
            .withProperty("powered", this.isJoining ? "true" : "false");

        this.setBlock(this.buttonLocation, button);
    }

    private void connect() {
        if(!this.isJoining) return;

        this.playSound(Sound.sound(SoundEvent.ENTITY_PLAYER_LEVELUP, Sound.Source.PLAYER, 1f, 1f));

        if(ConnectInfo.availableTickets < 1) {
            this.everyMember(p -> p.sendActionBar(Component.text("Keine Spielzeit verfügbar!", NamedTextColor.RED)));
        } else {
            MinecraftServer.getSchedulerManager().scheduleTask(() -> this.everyMember(p -> {
                p.addEffect(new Potion(PotionEffect.DARKNESS, 0, 5 * Ticks.TICKS_PER_SECOND));
                p.sendActionBar(Component.text("Verbinde...", NamedTextColor.GREEN));
                PluginMessageUtil.connect(p, this.targetServer);
            }), TaskSchedule.seconds(1), TaskSchedule.stop());
        }

        MinecraftServer.getSchedulerManager().scheduleTask(
            () -> {
                this.isJoining = false;
                this.update();
            },
            TaskSchedule.seconds(10),
            TaskSchedule.stop()
        );
    }

    private void particleTick() {
        if(!this.isComplete) return;
        this.everyMember(p -> p.sendPacket(this.isJoining ? this.progressParticles : this.availableParticles));
    }

    private void everyMember(Consumer<Player> consumer) {
        this.getPlayers().forEach(consumer);
    }
}
