package de.fete;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.Architectures.onionArchitecture;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchCondition;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.lang.ConditionEvents;
import com.tngtech.archunit.lang.SimpleConditionEvent;

@AnalyzeClasses(packages = "de.fete", importOptions = ImportOption.DoNotIncludeTests.class)
class HexagonalArchitectureTest {

  @ArchTest
  static final ArchRule onionArchitectureIsRespected = onionArchitecture()
      .domainModels("de.fete.domain.model..")
      .domainServices("de.fete.domain.port.in..", "de.fete.domain.port.out..")
      .applicationServices("de.fete.application.service..")
      .adapter("web", "de.fete.adapter.in.web..")
      .adapter("persistence", "de.fete.adapter.out.persistence..")
      .adapter("config", "de.fete.config..");

  @ArchTest
  static final ArchRule domainMustNotDependOnAdapters = noClasses()
      .that().resideInAPackage("de.fete.domain..")
      .should().dependOnClassesThat().resideInAPackage("de.fete.adapter..");

  @ArchTest
  static final ArchRule domainMustNotDependOnApplication = noClasses()
      .that().resideInAPackage("de.fete.domain..")
      .should().dependOnClassesThat().resideInAPackage("de.fete.application..");

  @ArchTest
  static final ArchRule domainMustNotDependOnConfig = noClasses()
      .that().resideInAPackage("de.fete.domain..")
      .should().dependOnClassesThat().resideInAPackage("de.fete.config..");

  @ArchTest
  static final ArchRule inboundPortsMustBeInterfaces = classes()
      .that().resideInAPackage("de.fete.domain.port.in..")
      .should().beInterfaces();

  @ArchTest
  static final ArchRule outboundPortsMustBeInterfaces = classes()
      .that().resideInAPackage("de.fete.domain.port.out..")
      .should().beInterfaces();

  @ArchTest
  static final ArchRule domainMustNotUseSpring = noClasses()
      .that().resideInAPackage("de.fete.domain..")
      .should().dependOnClassesThat().resideInAPackage("org.springframework..");

  @ArchTest
  static final ArchRule webMustNotDependOnPersistence = noClasses()
      .that().resideInAPackage("de.fete.adapter.in.web..")
      .should().dependOnClassesThat().resideInAPackage("de.fete.adapter.out.persistence..");

  @ArchTest
  static final ArchRule persistenceMustNotDependOnWeb = noClasses()
      .that().resideInAPackage("de.fete.adapter.out.persistence..")
      .should().dependOnClassesThat().resideInAPackage("de.fete.adapter.in.web..");

  @ArchTest
  static final ArchRule webAdapterMustNotDependOnOutboundPorts = noClasses()
      .that().resideInAPackage("de.fete.adapter.in.web..")
      .should().dependOnClassesThat().resideInAPackage("de.fete.domain.port.out..");

  @ArchTest
  static final ArchRule domainModelsMustBeRecords = classes()
      .that().resideInAPackage("de.fete.domain.model..")
      .and().doNotHaveSimpleName("package-info")
      .should(beRecords());

  private static ArchCondition<JavaClass> beRecords() {
    return new ArchCondition<>("be records") {
      @Override
      public void check(JavaClass javaClass,
          ConditionEvents events) {
        boolean isRecord = javaClass.reflect().isRecord();
        if (!isRecord) {
          events.add(SimpleConditionEvent.violated(javaClass,
              javaClass.getFullName() + " is not a record"));
        }
      }
    };
  }
}
