package cz.jzitnik.game.handlers.place;

import cz.jzitnik.game.Game;
import cz.jzitnik.game.annotations.*;
import cz.jzitnik.game.entities.Block;
import cz.jzitnik.game.entities.items.InventoryItem;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import cz.jzitnik.game.logic.services.farmable.FarmableData;
import cz.jzitnik.game.sprites.TwoBlockSprite;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Random;

public class CustomAnnotationHandler implements CustomPlaceHandler {
    private final Class<?> clazz;
    private final DefaultPlaceHandler defaultPlaceHandler = new DefaultPlaceHandler();
    private final Random random = new Random();

    private record BlockDrop(String drops, int percentage) {
    }

    public CustomAnnotationHandler(Class<?> clazz) {
        this.clazz = clazz;
    }

    @Override
    public boolean place(Game game, int x, int y) {
        boolean place = true;
        boolean customPlace = false;
        if (clazz.isAnnotationPresent(PlaceOnSolid.class)) {
            place = placeOnSolid(game, x, y);
        }

        if (place && clazz.isAnnotationPresent(Farmable.class)) {
            place = placeFarmable(game, x, y);
        }

        if (place && clazz.isAnnotationPresent(TwoblockBlock.class)) {
            var blocksTop = game.getWorld()[y - 1][x];
            if (!blocksTop.stream().allMatch(block -> block.getBlockId().equals("air"))) {
                place = false;
            }
        }

        if (place && clazz.isAnnotationPresent(TwoblockBlock.class)) {
            var inventory = game.getInventory();
            var blocks = game.getWorld()[y][x];
            var blocksTop = game.getWorld()[y - 1][x];

            Block block = inventory.getItemInHand().get().getBlock().get();
            block.setSpriteState(TwoBlockSprite.TwoBlockSpriteState.BOTTOM);
            blocks.add(block);
            blocks.removeAll(blocks.stream().filter(Block::isFlowing).toList());

            Block block2 = ItemBlockSupplier.getBlock(block.getBlockId());
            block2.setSpriteState(TwoBlockSprite.TwoBlockSpriteState.TOP);
            blocksTop.add(block2);
            blocksTop.removeAll(blocksTop.stream().filter(Block::isFlowing).toList());

            inventory.decreaseItemInHand();

            customPlace = true;
            return true;
        }

        if (!customPlace && place) {
            return defaultPlaceHandler.place(game, x, y);
        }

        return false;
    }

    @Override
    public boolean mine(Game game, int x, int y) {
        boolean dropDefault = true;

        if (clazz.isAnnotationPresent(Farmable.class)) {
            var blocks = game.getWorld()[y][x];
            var blockx = blocks.stream().filter(block -> !block.getBlockId().equals("air")).findFirst().get();

            var data = (FarmableData) blockx.getData();

            if (data.getState() == 2) {
                dropDefault = false;
                var annotation = clazz.getAnnotation(Farmable.class);
                var growDrop = annotation.value();
                var seedDrop = blockx.getDrops().get(0);
                var seedDropAmount = random.nextInt(4);

                if (seedDropAmount != 0) {
                    game.getInventory().addItem(new InventoryItem(seedDropAmount, seedDrop));
                }

                game.getInventory().addItem(ItemBlockSupplier.getItem(growDrop));
            }
        }

        if (clazz.isAnnotationPresent(ResetDataOnMine.class)) {
            resetDataOnMine(game, x, y);
        }

        if (clazz.isAnnotationPresent(ResetSpriteStateOnMine.class)) {
            var blocks = game.getWorld()[y][x];

            var blockx = blocks.stream().filter(block -> !block.getBlockId().equals("air")).findFirst().get();
            blockx.setSpriteState();
        }


        if (clazz.isAnnotationPresent(CustomDrops.class) || clazz.isAnnotationPresent(CustomDrop.class)) {
            var annotations = clazz.isAnnotationPresent(CustomDrops.class)
                    ? clazz.getAnnotation(CustomDrops.class).value()
                    : new CustomDrop[] { clazz.getAnnotation(CustomDrop.class) };

            var hashmap = new HashMap<String, BlockDrop>();
            for (CustomDrop customDrop : annotations) {
                hashmap.put(customDrop.tool(), new BlockDrop(customDrop.drops(), customDrop.percentage()));
            }

            if (game.getInventory().getItemInHand().isPresent()
                    && hashmap.containsKey(game.getInventory().getItemInHand().get().getId())) {
                BlockDrop blockDrop = hashmap.get(game.getInventory().getItemInHand().get().getId());
                Random random = new Random();
                int num = random.nextInt(100);

                if (num < blockDrop.percentage) {
                    dropDefault = false;
                    game.getInventory().addItem(ItemBlockSupplier.getItem(blockDrop.drops()));
                }
            }
        }

        if (dropDefault && clazz.isAnnotationPresent(BlockDropPercentage.class)) {
            dropDefault = blockDropPercentage(game, x, y);
        }

        if (clazz.isAnnotationPresent(TwoblockBlock.class)) {
            var blocksTop = game.getWorld()[y - 1][x];
            if (blocksTop.stream().anyMatch(i -> !i.getBlockId().equals("air") && !i.isMob())) {
                blocksTop.removeAll(
                        blocksTop.stream().filter(i -> !i.getBlockId().equals("air") && !i.isMob()).toList());
            } else {
                var blocksBottom = game.getWorld()[y + 1][x];
                blocksBottom.removeAll(
                        blocksBottom.stream().filter(i -> !i.getBlockId().equals("air") && !i.isMob()).toList());
            }
        }

        defaultPlaceHandler.mine(game, x, y);

        return dropDefault;
    }

    private boolean blockDropPercentage(Game game, int x, int y) {
        var annotation = clazz.getAnnotation(BlockDropPercentage.class);
        int percentage = annotation.value();

        Random random = new Random();
        return random.nextInt(100) < percentage;
    }

    private boolean placeOnSolid(Game game, int x, int y) {
        var blocksBottom = game.getWorld()[y + 1][x];

        if (blocksBottom.stream().allMatch(Block::isGhost)) {
            return false;
        }

        return true;
    }

    private boolean placeFarmable(Game game, int x, int y) {
        var blocksBottom = game.getWorld()[y + 1][x];

        if (blocksBottom.stream().noneMatch(block -> block.getBlockId().equals("farmland"))) {
            return false;
        }

        return true;
    }

    private void resetDataOnMine(Game game, int x, int y) {
        var blocks = game.getWorld()[y][x].stream().filter(i -> !i.getBlockId().equals("air")).toList();

        for (Block block : blocks) {
            if (block.getData() == null) {
                continue;
            }

            try {
                Constructor<?> constructor = block.getData().getClass().getDeclaredConstructor();
                Object object = constructor.newInstance();
                block.setData(object);
            } catch (NoSuchMethodException | IllegalAccessException | InstantiationException
                    | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}
