package io.apicurio.registry.operator.unit;

import io.apicurio.registry.operator.Configuration;
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
import io.apicurio.registry.operator.api.v1.spec.UiSpec;
import io.apicurio.registry.operator.resource.ResourceFactory;
import io.apicurio.registry.operator.status.StatusManager;
import io.apicurio.registry.operator.status.ValidationErrorConditionManager;
import io.apicurio.registry.operator.unit.PodTemplateSpecArgumentProviders.AppNegativeTestCases;
import io.apicurio.registry.operator.unit.PodTemplateSpecArgumentProviders.AppPositiveTestCases;
import io.apicurio.registry.operator.unit.PodTemplateSpecArgumentProviders.TestCase;
import io.apicurio.registry.operator.unit.PodTemplateSpecArgumentProviders.UINegativeTestCases;
import io.apicurio.registry.operator.unit.PodTemplateSpecArgumentProviders.UIPositiveTestCases;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import org.eclipse.microprofile.config.ConfigProvider;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_APP_CONTAINER_NAME;
import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_UI_CONTAINER_NAME;
import static org.assertj.core.api.Assertions.assertThat;

public class PodTemplateSpecTest {

    private static final Logger log = LoggerFactory.getLogger(PodTemplateSpecTest.class);

    @ParameterizedTest
    @ArgumentsSource(AppPositiveTestCases.class)
    void testAppPositive(TestCase testCase) {
        log.info("Running test case: {}", testCase.getId());
        var primary = getPrimary();
        primary.getSpec().getApp().setPodTemplateSpec(testCase.getSpec());
        var expected = ResourceFactory.INSTANCE.getDefaultAppDeployment(primary).getSpec().getTemplate();
        preprocessTestCaseExpected(testCase.getExpected());
        assertThat(expected)
                .usingRecursiveComparison()
                .ignoringCollectionOrderInFields("spec.containers", "spec.containers.ports")
                .isEqualTo(testCase.getExpected());
        assertThat(testCase.getExpected())
                .usingRecursiveComparison()
                .ignoringCollectionOrderInFields("spec.containers", "spec.containers.ports")
                .isEqualTo(expected);
    }

    @ParameterizedTest
    @ArgumentsSource(AppNegativeTestCases.class)
    void testAppNegative(TestCase testCase) {
        log.info("Running test case: {}", testCase.getId());
        var primary = getPrimary();
        try {
            primary.getSpec().getApp().setPodTemplateSpec(testCase.getSpec());
            ResourceFactory.INSTANCE.getDefaultAppDeployment(primary);
            assertThat(StatusManager.get(primary).getConditionManager(ValidationErrorConditionManager.class).hasErrors()).isTrue();
        } finally {
            StatusManager.clean(primary);
        }
    }

    @ParameterizedTest
    @ArgumentsSource(UIPositiveTestCases.class)
    void testUIPositive(TestCase testCase) {
        log.info("Running test case: {}", testCase.getId());
        var primary = getPrimary();
        primary.getSpec().getUi().setPodTemplateSpec(testCase.getSpec());
        var expected = ResourceFactory.INSTANCE.getDefaultUIDeployment(primary).getSpec().getTemplate();
        preprocessTestCaseExpected(testCase.getExpected());
        assertThat(expected)
                .usingRecursiveComparison()
                .ignoringCollectionOrderInFields("spec.containers", "spec.containers.ports")
                .isEqualTo(testCase.getExpected());
        assertThat(testCase.getExpected())
                .usingRecursiveComparison()
                .ignoringCollectionOrderInFields("spec.containers", "spec.containers.ports")
                .isEqualTo(expected);
    }

    @ParameterizedTest
    @ArgumentsSource(UINegativeTestCases.class)
    void testUINegative(TestCase testCase) {
        log.info("Running test case: {}", testCase.getId());
        var primary = getPrimary();
        try {
            primary.getSpec().getUi().setPodTemplateSpec(testCase.getSpec());
            ResourceFactory.INSTANCE.getDefaultUIDeployment(primary);
            assertThat(StatusManager.get(primary).getConditionManager(ValidationErrorConditionManager.class).hasErrors()).isTrue();
        } finally {
            StatusManager.clean(primary);
        }
    }

    private static ApicurioRegistry3 getPrimary() {
        var primary = new ApicurioRegistry3();
        primary.setMetadata(new ObjectMeta());
        primary.getMetadata().setName("test");
        primary.getMetadata().setNamespace("test");
        primary.setSpec(new ApicurioRegistry3Spec());
        primary.getSpec().setApp(new AppSpec());
        primary.getSpec().setUi(new UiSpec());
        return primary;
    }

    private static void preprocessTestCaseExpected(PodTemplateSpec expected) {
        // Set the version label if it's a placeholder.
        expected.getMetadata().getLabels().computeIfPresent("app.kubernetes.io/version", (k, v) -> {
            if ("PLACEHOLDER_VERSION".equals(v)) {
                return ConfigProvider.getConfig().getValue("registry.version", String.class);
            } else {
                return v;
            }
        });
        // Set the image if it's a placeholder (image can be configured, so we don't know its value in advance).
        expected.getSpec().getContainers().forEach(container -> {
            if (REGISTRY_APP_CONTAINER_NAME.equals(container.getName()) && "PLACEHOLDER_REGISTRY_APP_IMAGE".equals(container.getImage())) {
                container.setImage(Configuration.getAppImage());
            } else if (REGISTRY_UI_CONTAINER_NAME.equals(container.getName()) && "PLACEHOLDER_REGISTRY_UI_IMAGE".equals(container.getImage())) {
                container.setImage(Configuration.getUIImage());
            }
        });
    }
}
