/**
 * Copyright © 2012-2025 by Data Virtuality GmbH
 */
package org.teiid.i18n;

import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.DynamicTest;
import org.reflections.Reflections;
import org.teiid.core.BundleUtil;

import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;

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

/**
 * Utility to generate dynamic tests for i18n bundles based on BundleUtil.Event enums.
 *
 * @author Serhii Bashniak
 * @date 2025.05.02
 */
public class InternalizationTestUtil {

    /**
     * @param i18nPackage   i18n.properties package (e.g. "org.teiid")
     * @param pluginPackage base package of plugins (e.g. "org.teiid")
     * @param expectedSkips number of expected skipped events (missing in i18n.properties)
     * @return stream of dynamic JUnit tests
     */
    public Stream<DynamicTest> generateTests(
            String i18nPackage,
            String pluginPackage,
            int expectedSkips
    ) {
        String i18nBase = i18nPackage.isEmpty() ? "i18n" : i18nPackage + ".i18n";

        Reflections reflections = new Reflections(pluginPackage);
        Set<Class<? extends BundleUtil.Event>> eventEnums =
                reflections.getSubTypesOf(BundleUtil.Event.class);

        if (eventEnums.isEmpty()) {
            return Stream.of(DynamicTest.dynamicTest("Check count of Events", () ->
                    fail("Not found any Event enums in package: " + pluginPackage)
            ));
        }

        ResourceBundle bundle = ResourceBundle.getBundle(i18nBase);
        BundleUtil util = new BundleUtil(i18nBase, i18nBase, bundle);

        AtomicInteger skipCounter = new AtomicInteger();

        Stream<DynamicTest> tests = eventEnums.stream()
                .flatMap(enumClass -> {
                    Class<?> declaringClass = enumClass.getDeclaringClass();

                    if (!declaringClass.getPackageName().equals(pluginPackage)) {
                        return Stream.empty();
                    }

                    if (!"Event".equals(enumClass.getSimpleName())) {
                        return Stream.empty();
                    }

                    String className = declaringClass.getSimpleName();
                    if (className.endsWith("Test") || className.endsWith("Tests")) {
                        return Stream.empty();
                    }

                    if (isJUnitTestClass(declaringClass)) {
                        return Stream.empty();
                    }

                    return Stream.of(enumClass.getEnumConstants())
                            .map(constant -> DynamicTest.dynamicTest(
                                    declaringClass.getSimpleName() + "." + constant,
                                    () -> {
                                        String code = constant.toString();

                                        Assumptions.assumeTrue(
                                                bundle.containsKey(code),
                                                () -> {
                                                    skipCounter.incrementAndGet();
                                                    return "Skipping: no key “" + code + "” in bundle";
                                                }
                                        );

                                        String template = bundle.getString(code);
                                        assertNotNull(template, "Template is null for " + code);
                                        assertFalse(template.isEmpty(), "Template is empty for " + code);

                                        Object[] args = {1, 2};
                                        String formatted = new MessageFormat(template).format(args);
                                        String actual = util.gs(constant, args);
                                        String expected = code + " " + formatted;

                                        assertEquals(expected, actual,
                                                () -> "Formatted message mismatch for " + code);
                                    }
                            ));
                });

        Stream<DynamicTest> finalCheck = Stream.of(
                DynamicTest.dynamicTest("Check skipped count", () ->
                        assertEquals(expectedSkips,
                                skipCounter.get(),
                                "Unexpected number of skipped tests")
                )
        );

        return Stream.concat(tests, finalCheck);
    }

    private boolean isJUnitTestClass(Class<?> clazz) {
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(org.junit.jupiter.api.Test.class) ||
                    method.isAnnotationPresent(org.junit.jupiter.api.TestFactory.class) ||
                    method.isAnnotationPresent(org.junit.jupiter.api.RepeatedTest.class)) {
                return true;
            }
        }
        return false;
    }
}
