package architecturetests;

import com.tngtech.archunit.core.domain.JavaModifier;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.EvaluationResult;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.RawWrapper;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.junit.jupiter.api.Test;

import java.io.Serializable;
import java.util.Set;

import static architecturetests.ArchUnitHelper.*;
import static org.assertj.core.api.Assertions.assertThat;

public class InternalStructureOfEventClassesUsingCorrectInterfacesAndAttributeTypesTest {

    static final String PACKAGE_BESUCHERPORTAL_API_EVENTS = ArchUnitHelper.PACKAGE_PREFIX + ".besucherportal.api_contract.event";
    static final String PACKAGE_BACKOFFICE_API_EVENTS     = ArchUnitHelper.PACKAGE_PREFIX + ".backoffice.api_contract.event";
    static final String PACKAGE_SECURITY_API_EVENTS       = ArchUnitHelper.PACKAGE_PREFIX + ".security.api_contract.event";
    static final String PACKAGE_TICKETING_API_EVENTS      = ArchUnitHelper.PACKAGE_PREFIX + ".ticketing.api_contract.event";

    static final GivenClassesConjunction eventClasses = ArchRuleDefinition.classes()
            .that()
            .resideInAnyPackage(PACKAGE_BESUCHERPORTAL_API_EVENTS,
                    PACKAGE_BACKOFFICE_API_EVENTS,
                    PACKAGE_SECURITY_API_EVENTS,
                    PACKAGE_TICKETING_API_EVENTS)
            .and()
            .areNotEnums()
            .and()
            .areNotInnerClasses()
            .and()
            .areNotNestedClasses()
            .and()
            .doNotHaveModifier(JavaModifier.ABSTRACT) // ignore AbstractEvent
            .and()
            .haveSimpleNameNotEndingWith("Events"); // ignore the ...Events classes, as they simply collect the ...Event classes

    @Test
    void test_EventClassesAreImplementingEvent() {
        // arrange
        ArchRule archRule = eventClasses
                .should()
                .implement(Event.class);

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesAreImplementingIdentifiableAndVersionableAndSerializable() {
        // arrange
        ArchRule archRule = eventClasses
                .should()
                .implement(Identifiable.class)
                .andShould()
                .implement(Versionable.class)
                .andShould()
                .implement(Serializable.class);

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveIdFieldUsingStrongType() {
        // arrange
        String idField = "id";

        ArchRule archRule = eventClasses
                .should(hasFieldOfType(idField, Identifiable.Id.class));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveCorrelationIdFieldUsingStrongType() {
        // arrange
        String correlationIdField = "correlationId";

        ArchRule archRule = eventClasses
                .should(hasFieldOfType(correlationIdField, Identifiable.Id.class));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveVersionFieldUsingStrongType() {
        // arrange
        String versionField = "version";

        ArchRule archRule = eventClasses
                .should(hasFieldOfType(versionField, Versionable.Version.class));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveIdFieldWhichIsPublicFinal() {
        // arrange
        String idField = "id";

        ArchRule archRule = eventClasses
                .should(hasFieldPublicFinal(idField));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveCorrelationIdFieldWhichIsPublicFinal() {
        // arrange
        String correlationIdField = "correlationId";

        ArchRule archRule = eventClasses
                .should(hasFieldPublicFinal(correlationIdField));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveVersionFieldWhichIsPrivateStatic() {
        // arrange
        String versionField = "version";

        ArchRule archRule = eventClasses
                .should(hasFieldPrivateStatic(versionField, false));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveEventAsSuffixClassName() {
        // arrange
        String clazzNameSuffix = "Event";

        ArchRule archRule = eventClasses
                .should()
                .haveSimpleNameEndingWith(clazzNameSuffix);

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveOnlyFieldsImplementingRawWrapper() {
        // arrange
        Set<Class<?>>        interfaceTypes = Set.of(RawWrapper.class);
        Set<String>   fieldsToBeFilteredOut = Set.of();
        String fieldSuffixToBeFilteredOut   = "Id";

        ArchRule archRule = eventClasses
                .should(hasOnlyFieldsImplementing(interfaceTypes, fieldsToBeFilteredOut, fieldSuffixToBeFilteredOut));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }

    @Test
    void test_EventClassesHaveExternalIdFieldsUsingStrongType() {
        //We check here that any attribute with suffix "...Id" in an event class is of type Identifiable.Id.
        // Actually it would be better to check that events do not 'carry' data by themselves but only in
        // TO classes wrapping this data. But this is not so easy to do.

        // arrange
        String idFieldSuffix = "Id";

        ArchRule archRule = eventClasses
                .should(hasFieldsWithSuffixOfType(idFieldSuffix, Identifiable.Id.class));

        // act
        EvaluationResult evaluationResult = archRule.evaluate(ArchUnitHelper.allJavaClassesToCheck);

        // assert
        assertThat(evaluationResult.getFailureReport().getDetails()).isEmpty();
    }
}
