package me.loganb1max.minionsplus.manager;

import com.google.gson.*;
import lombok.Getter;
import me.loganb1max.minionsplus.MinionsPlus;
import me.loganb1max.minionsplus.menu.MinionMenu;
import me.loganb1max.minionsplus.model.Minion;
import me.loganb1max.minionsplus.model.minions.ButcherMinion;
import me.loganb1max.minionsplus.model.minions.MinerMinion;
import me.loganb1max.minionsplus.util.ItemUtil;
import me.loganb1max.minionsplus.util.Text;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitTask;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;

public class MinionManager implements Listener {

	private final MinionsPlus plugin;
	private final BukkitTask animationTask;
	private final BukkitTask tickTask;
	@Getter private final Map<LivingEntity, ButcherMinion> butcherKillMap = new HashMap<>();

	@Getter private Set<Minion> minions = new HashSet<>();

	public MinionManager(final MinionsPlus plugin) {
		this.plugin = plugin;
		this.animationTask = Bukkit.getScheduler().runTaskTimer(this.plugin, () -> this.minions.forEach(Minion::animationTick), 1, 1);
		final int actionSpeed = this.plugin.getConfig().getInt("Action-Speed", 20);
		this.tickTask = Bukkit.getScheduler().runTaskTimer(this.plugin, () -> this.minions.forEach(Minion::tick), actionSpeed, actionSpeed);
		Bukkit.getPluginManager().registerEvents(this, this.plugin);
		loadMinions();
	}

	public void close() {
		this.animationTask.cancel();
		this.tickTask.cancel();
		HandlerList.unregisterAll(this);
		saveMinions();
	}

	private void loadMinions() {
		final File dataFile = new File(this.plugin.getDataFolder(), "minions.json");
		if (!dataFile.exists()) return;
		final Gson gson = new Gson();
		try (final BufferedReader reader = Files.newBufferedReader(dataFile.toPath(), StandardCharsets.UTF_8)) {
			JsonObject data = gson.fromJson(reader, JsonObject.class);
			JsonArray minions = data.get("minions").getAsJsonArray();
			for (JsonElement el : minions) {
				try {
					Minion minion = Minion.deserialize(this.plugin, el);
					this.minions.add(minion);
				} catch (Exception e) {
					this.plugin.getLogger().severe("Failed to load minion.");
					e.printStackTrace();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void saveMinions() {
		final File dataFile = new File(this.plugin.getDataFolder(), "minions.json");
		final Gson gson = new Gson();
		try (final BufferedWriter writer = Files.newBufferedWriter(dataFile.toPath(), StandardCharsets.UTF_8)) {
			JsonObject data = new JsonObject();
			JsonArray minions = new JsonArray();

			for (Minion minion : this.minions) {
				try {
					minions.add(minion.serialize());
				} catch (Exception e) {
					this.plugin.getLogger().severe("Failed to save minion: " + minion.getOwnerName() + " : " + minion.getName());
					e.printStackTrace();
				}
			}

			data.add("minions", minions);
			writer.write(gson.toJson(data));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@EventHandler(priority = EventPriority.HIGHEST)
	public void onDamage(final EntityDamageEvent e) {
		if (!(e.getEntity() instanceof ArmorStand)) return;
		if (this.minions.stream().map(Minion::getId).filter(Objects::nonNull).anyMatch(uuid -> uuid.equals(e.getEntity().getUniqueId()))) {
			e.setCancelled(true);
		}
	}

	@EventHandler(priority = EventPriority.HIGHEST)
	public void onRightClick(final PlayerArmorStandManipulateEvent e) {
		this.minions.stream().filter(minion -> minion.getId().equals(e.getRightClicked().getUniqueId())).findFirst().ifPresent(minion -> {
			e.setCancelled(true);
			if (!minion.getOwner().equals(e.getPlayer().getUniqueId())) {
				e.getPlayer().sendMessage(Text.color("&cOnly the owner of the minion can interact with it."));
				return;
			}
			if (e.getPlayer().isSneaking()) {
				minion.despawn(e.getPlayer());
				return;
			}
			new MinionMenu(minion, e.getPlayer());
		});
	}

	@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
	public void onPlace(PlayerInteractEvent e) {
		if (!e.hasItem() || !Minion.isMinion(e.getItem())) return;
		e.setCancelled(true);
		if (!e.hasBlock()) return;
		final Location loc = e.getClickedBlock().getRelative(e.getBlockFace()).getLocation().add(0.5, 0, 0.5);
		final Minion minion = Minion.of(e.getItem(), loc);
		minion.spawn(loc);
		ItemUtil.consumeItem(e.getPlayer().getInventory(), 1, e.getItem());
	}

	@EventHandler
	public void onLivingEntityDeath(final EntityDeathEvent e) {
		if (!this.butcherKillMap.containsKey(e.getEntity())) return;
		final ButcherMinion minion = this.butcherKillMap.remove(e.getEntity());
		final List<ItemStack> drops = e.getDrops();
		minion.getLinkedBlocks().stream().filter(b -> b.getState() instanceof InventoryHolder).forEachOrdered(b -> {
			InventoryHolder inv = (InventoryHolder) b.getState();
			final Collection<ItemStack> remaining = inv.getInventory().addItem(drops.toArray(new ItemStack[] {})).values();
			drops.clear();
			drops.addAll(remaining);
		});
		if (!drops.isEmpty()) {
			drops.forEach(item -> minion.getLocation().getWorld().dropItemNaturally(minion.getLocation(), item));
		}
	}

}
