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.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 RoleProjectionsViewTest {
    @Test
    void exposesRoleProjectionsAndTheirCompileUnits() {
        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 projections = RoleProjectionsView.of(view);
        var productionProjection = projections.requireProjection(browser, production);
        var qaProjection = projections.requireProjection(browser, qa);

        assertEquals(Set.of(productionProjection, qaProjection), projections.getProjections());
        assertEquals(Set.of(productionProjection, qaProjection), projections.getProjectionsForVariant(browser));
        assertEquals(Set.of(qaProjection), projections.getProjectionsForRole(qa));
        assertEquals(Set.of(new CompileUnit(browser, main)), projections.getUnits(productionProjection));
        assertEquals(Set.of(new CompileUnit(browser, main), new CompileUnit(browser, test)), projections.getUnits(qaProjection));
        assertEquals(Set.of(main, test), projections.getLayers(qaProjection));
        assertTrue(projections.contains(browser, production));
    }

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

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

        var projections = RoleProjectionsView.of(view);

        var ex = assertThrows(IllegalArgumentException.class, () -> projections.requireProjection(node, production));
        assertTrue(ex.getMessage().contains("Role projection for variant 'node' and role 'production' 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;
        }
    }
}
