package cz.jzitnik.game.handlers.place;

import cz.jzitnik.game.annotations.*;
import cz.jzitnik.game.entities.items.ItemBlockSupplier;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.*;

class PlaceHandlerTest {
    @Test
    @DisplayName("All classes annotated with @PlaceHandler must implement CustomPlaceHandler")
    void allClassesPickupImplementHandler() {
        Reflections reflections = new Reflections("cz.jzitnik.game.handlers.pickup.handlers");
        Set<Class<?>> handlerClasses = reflections.getTypesAnnotatedWith(cz.jzitnik.game.annotations.PlaceHandler.class);

        for (Class<?> clazz : handlerClasses) {
            boolean implementsClass = CustomPlaceHandler.class.isAssignableFrom(clazz);
            assertTrue(implementsClass, "Class " + clazz.getName() + " annotated with @PlaceHandler does not implement CustomPlaceHandler.");
        }
    }

    @Test
    @DisplayName("All blocks in annotation @PlaceHandler must exist")
    void allBlocksMustExist() {
        Reflections reflections = new Reflections("cz.jzitnik.game.handlers.place.handlers");
        Set<Class<?>> handlerClasses = reflections
                .getTypesAnnotatedWith(cz.jzitnik.game.annotations.PlaceHandler.class);

        for (Class<?> clazz : handlerClasses) {
            cz.jzitnik.game.annotations.PlaceHandler annotation = clazz
                    .getAnnotation(cz.jzitnik.game.annotations.PlaceHandler.class);
            var id = annotation.value();
            assertDoesNotThrow(() -> ItemBlockSupplier.getBlock(id), "Block " + id + " does not exist but is in annotation @PlaceHandler in class " + clazz.getName());
        }
    }

    /*@Test
    @DisplayName("All blocks must be annotated with @BlockRegistry if they are annotated with certain other annotations")
    void checkAnnotationsWithBlockRegistry() {
        // Define annotations and their names
        Object[][] annotationsToCheck = {
                {PlaceOnSolid.class, "@PlaceOnSolid"},
                {ResetDataOnMine.class, "@ResetDataOnMine"},
                {BlockDropPercentage.class, "@BlockDropPercentage"},
                {BreakableByWater.class, "@BreakableByWater"},
                {BreaksByPlace.class, "@BreaksByPlace"},
                {BreaksFalling.class, "@BreaksFalling"},
                {Fuel.class, "@Fuel"},
                {Smeltable.class, "@Smeltable"},
        };

        // Loop through each annotation and check for @BlockRegistry
        for (Object[] annotationData : annotationsToCheck) {
            Class<?> annotationClass = (Class<?>) annotationData[0];
            String annotationName = (String) annotationData[1];
            checkBlockRegistryAnnotationForClassesWithAnnotation(annotationClass, annotationName);
        }
    }*/

    @Test
    void checkAnnotationValidity() {
        Reflections reflections = new Reflections("cz.jzitnik.game");

        Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(RequireAnnotation.class);

        for (Class<?> annotation : annotations) {
            if (!annotation.isAnnotation()) {
                continue;
            }

            Set<Class<?>> classes = reflections.getTypesAnnotatedWith((Class<? extends Annotation>) annotation);
            var anot = annotation.getAnnotation(RequireAnnotation.class);

            for (Class<?> clazz : classes) {
                for (Class<? extends Annotation> annotationRequired : anot.value()) {
                    if (!clazz.isAnnotationPresent(annotationRequired)) {
                        fail("All classes annotated with @" + annotation.getSimpleName() + " must be also annotated with @" + annotationRequired.getSimpleName() + " but class " + clazz.getName() + " isn't.");
                    }
                }
            }
        }
    }

    @Test
    @DisplayName("All blocks annotated with @ResetDataOnMine must have data class.")
    void dataOnMineHasDataTest() {
        Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks");
        Set<Class<?>> handlerClasses = reflections
                .getTypesAnnotatedWith(ResetDataOnMine.class);

        for (Class<?> clazz : handlerClasses) {
            var annotation = clazz.getAnnotation(BlockRegistry.class);
            var block = ItemBlockSupplier.getBlock(annotation.value());
            assertNotNull(block.getData(), "Block " + clazz.getName() + " annotated with @ResetDataOnMine does not have default Data set.");
        }
    }

    @Test
    @DisplayName("All blocks annotated with @ResetDataOnMine must have data class with default constructor")
    void dataOnMineDefaultConstructorTest() {
        Reflections reflections = new Reflections("cz.jzitnik.game.entities.items.registry.blocks");
        Set<Class<?>> handlerClasses = reflections
                .getTypesAnnotatedWith(ResetDataOnMine.class);

        for (Class<?> clazz : handlerClasses) {
            var annotation = clazz.getAnnotation(BlockRegistry.class);
            var block = ItemBlockSupplier.getBlock(annotation.value());
            var dataClass = block.getData().getClass();
            var hasDefaultConstructor = Arrays.stream(dataClass.getDeclaredConstructors()).anyMatch(constructor -> constructor.getParameterCount() == 0);
            assertTrue(hasDefaultConstructor, "Block " + clazz.getName() + " annotated with @ResetDataOnMine does not have Data that has default constructor.");
        }
    }
}