package org.implab.gradle.variants.sources;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.lang.reflect.Constructor;
import java.util.Set;

import org.gradle.api.InvalidUserDataException;
import org.implab.gradle.variants.core.Layer;
import org.implab.gradle.variants.core.Role;
import org.implab.gradle.variants.core.Variant;
import org.implab.gradle.variants.core.VariantsView;
import org.implab.gradle.variants.core.VariantsView.VariantRoleLayer;
import org.junit.jupiter.api.Test;

class CompileUnitsViewTest {
    @Test
    void deduplicatesCompileUnitsAcrossRolesAndExposesParticipatingRoles() {
        var browser = new TestVariant("browser");
        var main = new TestLayer("main");
        var test = new TestLayer("test");
        var production = new TestRole("production");
        var qa = new TestRole("test");

        var view = view(
                Set.of(main, test),
                Set.of(production, qa),
                Set.of(browser),
                Set.of(
                        new VariantRoleLayer(browser, production, main),
                        new VariantRoleLayer(browser, qa, main),
                        new VariantRoleLayer(browser, qa, test)));

        var units = CompileUnitsView.of(view);
        var browserMain = units.requireUnit(browser, main);
        var browserTest = units.requireUnit(browser, test);

        assertEquals(2, units.getUnits().size());
        assertEquals(Set.of(browserMain, browserTest), units.getUnitsForVariant(browser));
        assertEquals(Set.of(production, qa), units.getRoles(browserMain));
        assertEquals(Set.of(qa), units.getRoles(browserTest));
        assertTrue(units.contains(browser, main));
        assertTrue(units.contains(browser, test));
    }

    @Test
    void rejectsMissingCompileUnitLookup() {
        var browser = new TestVariant("browser");
        var main = new TestLayer("main");
        var missing = new TestLayer("missing");
        var production = new TestRole("production");

        var view = view(
                Set.of(main),
                Set.of(production),
                Set.of(browser),
                Set.of(new VariantRoleLayer(browser, production, main)));

        var units = CompileUnitsView.of(view);

        var ex = assertThrows(InvalidUserDataException.class, () -> units.requireUnit(browser, missing));
        assertTrue(ex.getMessage().contains("Compile unit for variant 'browser' and layer 'missing' not found"));
    }

    private static VariantsView view(
            Set<Layer> layers,
            Set<Role> roles,
            Set<Variant> variants,
            Set<VariantRoleLayer> entries) {
        try {
            Constructor<VariantsView> ctor = VariantsView.class.getDeclaredConstructor(
                    Set.class,
                    Set.class,
                    Set.class,
                    Set.class);
            ctor.setAccessible(true);
            return ctor.newInstance(layers, roles, variants, entries);
        } catch (Exception e) {
            throw new RuntimeException("Unable to create VariantsView fixture", e);
        }
    }

    private record TestVariant(String value) implements Variant {
        @Override
        public String getName() {
            return value;
        }
    }

    private record TestLayer(String value) implements Layer {
        @Override
        public String getName() {
            return value;
        }
    }

    private record TestRole(String value) implements Role {
        @Override
        public String getName() {
            return value;
        }
    }
}
