package jesse.keeblarcraft.CustomBlocks.BlockEntities;

import java.util.ArrayList;
import java.util.HashMap;

import jesse.keeblarcraft.AttributeMgr.AttributeMgr;
import jesse.keeblarcraft.Callbacks.MobSpawnCallback;
import jesse.keeblarcraft.FactionMgr.FactionConfig;
import jesse.keeblarcraft.FactionMgr.FactionManager;
import jesse.keeblarcraft.FactionMgr.Callbacks.PlayerEnteredBaseCallback;
import jesse.keeblarcraft.FactionMgr.Callbacks.PlayerExitedBaseCallback;
import jesse.keeblarcraft.FactionMgr.Callbacks.PlayerInBaseCallback;
import jesse.keeblarcraft.FactionMgr.FactionTier.FactionTierEnum;
import jesse.keeblarcraft.GuiMgr.FactionBlockScreenHandler;
import jesse.keeblarcraft.Utils.CommonStructures.Position3d;
import jesse.keeblarcraft.world.ImplementedInventory;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.inventory.Inventories;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.screen.PropertyDelegate;
import net.minecraft.screen.ScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public class FactionBlockEntity extends BlockEntity implements ExtendedScreenHandlerFactory, ImplementedInventory {
    private final DefaultedList<ItemStack> inventory = DefaultedList.ofSize(7, ItemStack.EMPTY);

    private static final int DEFENSE_SLOT_ONE = 0;
    private static final int DEFENSE_SLOT_TWO = 1;
    private static final int OFFENSE_SLOT_ONE = 2;
    private static final int OFFENSE_SLOT_TWO = 3;
    private String faction; // Faction this block belongs to
    private static int factionPower = 0;
    private FactionTierEnum factionTier = FactionTierEnum.TIER_INVALID;
    Boolean stopMobSpawn = true;
    Boolean hasBuildFlight = true;
    Boolean hasSuperBeacon = true;
    Boolean hasItemRepair = true;
    Position3d storedBlockPos;
    private final double factionDefaultRadiusBlocks = 50; // 50 blocks
    private ArrayList<String> playersInRadius = new ArrayList<>();
    private HashMap<String, FactionConfig.VALID_FACTION_ROLES> factionPlayers;

    protected final PropertyDelegate propertyDelegate;

    public FactionBlockEntity(BlockPos pos, BlockState state, String faction) {
        this(pos, state);
        this.faction = faction;

        MobSpawnCallback.EVENT.register((world, mob) -> {
            HandleMobSpawn(world, mob);
            return ActionResult.PASS;
        });
    }

    public void SetFaction(String faction) {
        this.faction = faction;
    }

    public FactionBlockEntity(BlockPos pos, BlockState state) {
        super(BlockEntityRegistration.FACTION_BLOCK_ENTITY, pos, state);
        storedBlockPos = new Position3d(pos.getX(), pos.getY(), pos.getZ());
        this.propertyDelegate = new PropertyDelegate() {
            @Override
            public int get(int index) {
                // The only value we need to get/delegate is faction power
                return factionPower;
            }

            @Override
            public void set(int index, int value) {
                factionPower = value;
            }

            @Override
            public int size() {
                // We are only syncing 1 integer - faction power
                return 1;
            }

        };
    }

    // TODO: Make sure mobs are within range before targeting!
    private void HandleMobSpawn(World world, MobEntity mob) {
        if (world.isClient()) {
            return;
        }

        mob.kill();
    }

    @Override
    public Text getDisplayName() {
        return Text.literal("Faction Home Base Station"); // Replace with proper en_us format later
    }

    @Override
    public ScreenHandler createMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player) {
        return new FactionBlockScreenHandler(syncId, playerInventory, this, this.propertyDelegate);
    }

    @Override
    public void readNbt(NbtCompound nbt) {
        super.readNbt(nbt);
        Inventories.readNbt(nbt, inventory);
        factionPower = nbt.getInt("faction_block_entity.power");
    }

    @Override
    public void writeNbt(NbtCompound nbt) {
        super.writeNbt(nbt);
        // Write the inventory of the block
        Inventories.writeNbt(nbt, inventory);
        nbt.putInt("faction_block_entity.power", factionPower);
    }

    @Override
    public DefaultedList<ItemStack> getItems() {
        return inventory;
    }

    @Override
    public void writeScreenOpeningData(ServerPlayerEntity player, PacketByteBuf buf) {
        // Send position of this block entity from server->client->server etc
        buf.writeBlockPos(this.pos);
    }

    // This is uniquely only calculating the second coordinate on 'storedBlockPos' which is the center coordinate of the
    // hypothetical sphere
    private Boolean DistanceToCenterSphere(double x, double y, double z) {
        // Distance between two points
        double dist =   Math.ceil(Math.sqrt(
                        Math.abs(Math.pow(Math.floor(x) - storedBlockPos.x, 2)) +
                        Math.abs(Math.pow(Math.floor(y) - storedBlockPos.y, 2)) +
                        Math.abs(Math.pow(Math.floor(z) - storedBlockPos.z, 2))));

        // Arbitrarily for now; each point of faction power increases the distance by 10. Testing will confirm if this
        // is too much or too little. Making this configurable would probably be good too
        // Default radius; increased by power
        double factionBlockRadius = factionDefaultRadiusBlocks + (10 * factionPower);
        System.out.println("Dist: " + dist);

        return Math.ceil(dist) <= factionBlockRadius;
    }

    private Boolean IsPlayerInBounds(PlayerEntity player, BlockPos pos) {
        Boolean isNearBlock = false;
        String playerFaction = FactionManager.GetInstance().GetFactionOfPlayer(player.getUuidAsString());

        return playerFaction.equals(faction) && DistanceToCenterSphere(player.getX(), player.getY(), player.getZ());
    }

    // Call when the block attached to this entity is broken so we can send out our last messages to reset players correctly before perishing
    public void ResetBlock() {
        System.out.println("Reset block called. Is world null? " + (world==null?"YES":"NO"));
        if (world != null) {
            for (PlayerEntity player : world.getPlayers()) {
                Boolean isPlayerInFactionRadius = IsPlayerInBounds(player, pos);
                if (isPlayerInFactionRadius) {
                    ActionResult result = PlayerExitedBaseCallback.EVENT.invoker().interact(player, world, factionPower, factionTier);
                }
            }
        } else {
            System.out.println("Error, world is null and factionblockentity cannot reset players!");
        }
    }

    // Tick method is called 20 times a second
    public void tick(World world, BlockPos pos, BlockState state) {
        if (world.isClient() || faction == null) {
            return;
        }

        this.world = world;
        //TODO: The below values can ABSOLUTELY be moved to a one-time call-out when the block is created & when a player
        // leaves or joins a faction with a simple callback subscriber. Leaving because not doing now, BUT REFACTOR
        factionPlayers = FactionManager.GetInstance().GetFactionPlayers(faction);
        factionPower = FactionManager.GetInstance().GetFactionPower(faction);
        factionTier = FactionManager.GetInstance().GetFactionTier(faction);

        for (PlayerEntity player : world.getPlayers()) {
            Boolean isPlayerInFactionRadius = IsPlayerInBounds(player, pos);
            if (isPlayerInFactionRadius) {
                // Run individual faction modules for players here
                // First time entry callback check
                if (!playersInRadius.contains(player.getUuidAsString())) {
                    playersInRadius.add(player.getUuidAsString());
                    ActionResult result = PlayerEnteredBaseCallback.EVENT.invoker().interact(player, world, factionPower, factionTier);
                }

                // Invoke the flight attribute on this player
                if (hasBuildFlight) {
                    AttributeMgr.ApplyAttribute(player.getUuidAsString(), "faction_flight");
                    ActionResult result = PlayerInBaseCallback.EVENT.invoker().interact(player, world, factionPower, factionTier);
                }

                if (hasSuperBeacon) {
                    AttributeMgr.ApplyAttribute(player.getUuidAsString(), "faction_beacon");
                    // ActionResult result = PlayerInBaseCallback.EVENT.invoker().interact(player, world);
                }

                if (hasItemRepair) {
                    AttributeMgr.ApplyAttribute(player.getUuidAsString(), "faction_item_repair");
                }
            } else if (!isPlayerInFactionRadius && playersInRadius.contains(player.getUuidAsString())) {
                playersInRadius.remove(player.getUuidAsString());
                ActionResult result = PlayerExitedBaseCallback.EVENT.invoker().interact(player, world, factionPower, factionTier);
            }
        }
    }
}
