package architecturetests;

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.shared_kernel.EqualsByContent;
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 InternalStructureOfTOClassesUsingCorrectInterfacesAndAttributeTypesTest {

    static final String PACKAGE_BACKOFFICE_API_MODEL = ArchUnitHelper.PACKAGE_PREFIX + ".backoffice.api_contract.event.model..";
    static final String PACKAGE_SECURITY_API_MODEL   = ArchUnitHelper.PACKAGE_PREFIX + ".security.api_contract.event.model..";
    static final String PACKAGE_TICKETING_API_MODEL  = ArchUnitHelper.PACKAGE_PREFIX + ".ticketing.api_contract.event.model..";

    static final GivenClassesConjunction toClasses = ArchRuleDefinition.classes()
            .that()
            .resideInAnyPackage(PACKAGE_BACKOFFICE_API_MODEL,
                    PACKAGE_SECURITY_API_MODEL,
                    PACKAGE_TICKETING_API_MODEL)
            .and()
            .areNotEnums()
            .and()
            .areNotInnerClasses()
            .and()
            .areNotNestedClasses();

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

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

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

    @Test
    void test_TOClassesAreRecords() {
        // arrange
        ArchRule archRule = toClasses
                .should()
                .beRecords();

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

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

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

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

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

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

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

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

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

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

    //@Test
    void test_TOClassesHaveIdFieldWhichIsPrivateFinal() {
        // Actually this test is not needed as we test that all TO classes are records, see test_TOClassesAreRecords()
        // In a record, all its attributes are private final

        // arrange
        String idField = "id";

        ArchRule archRule = toClasses
                .should(hasFieldPrivateFinal(idField));

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

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

    //@Test
    void test_TOClassesHaveVersionFieldWhichIsPrivateFinal() {
        // Actually this test is not needed as we test that all TO classes are records, see test_TOClassesAreRecords()
        // In a record, all its attributes are private final

        // arrange
        String versionField = "version";

        ArchRule archRule = toClasses
                .should(hasFieldPrivateFinal(versionField));

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

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

    @Test
    void test_TOClassesHaveTOAsSuffixClassName() {
        // arrange
        String clazzNameSuffix = "TO";

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

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

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

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

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

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

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

    @Test
    void test_TOClassesHaveExternalIdFieldsUsingStrongType() {
        // arrange
        String idFieldSuffix = "Id";

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

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

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