package com.codebyte.api.architecture;

import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.GeneralCodingRules.NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS;

import com.tngtech.archunit.core.importer.ImportOption;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
import jakarta.persistence.Entity;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

/** Enforces the layering rules from the spec (§8). */
@AnalyzeClasses(packages = "com.codebyte.api", importOptions = ImportOption.DoNotIncludeTests.class)
class LayeredArchitectureTest {

    // The base package is com.codebyte.api, so a bare "..api.." would match the whole app.
    // The web layer lives in per-feature packages: com.codebyte.api.<feature>.api..
    private static final String API_LAYER = "com.codebyte.api.*.api..";

    @ArchTest
    static final ArchRule CONTROLLERS_DO_NOT_TOUCH_REPOSITORIES =
            noClasses()
                    .that()
                    .resideInAPackage(API_LAYER)
                    .should()
                    .dependOnClassesThat()
                    .resideInAPackage("..repository..")
                    .as("controllers (api layer) must not touch repositories directly");

    @ArchTest
    static final ArchRule ENTITIES_NOT_IN_API_PACKAGES =
            noClasses()
                    .that()
                    .areAnnotatedWith(Entity.class)
                    .should()
                    .resideInAPackage(API_LAYER)
                    .as("JPA entities must not live in an api package");

    @ArchTest
    static final ArchRule SERVICES_IN_SERVICE_PACKAGES =
            classes()
                    .that()
                    .areAnnotatedWith(Service.class)
                    .should()
                    .resideInAPackage("..service..")
                    .as("@Service beans belong in a service package");

    @ArchTest
    static final ArchRule REPOSITORIES_IN_REPOSITORY_PACKAGES =
            classes()
                    .that()
                    .areAnnotatedWith(Repository.class)
                    .or()
                    .haveSimpleNameEndingWith("Repository")
                    .should()
                    .resideInAPackage("..repository..")
                    .as("repositories belong in a repository package");

    @ArchTest
    static final ArchRule NO_JAVA_UTIL_DATE =
            noClasses()
                    // The SSL package is exempt: X509Certificate.getNotAfter() returns
                    // java.util.Date,
                    // an unavoidable JCA interop boundary (we convert to java.time immediately).
                    .that()
                    .resideOutsideOfPackage("com.codebyte.api.config.ssl..")
                    .should()
                    .dependOnClassesThat()
                    .areAssignableTo(java.util.Date.class)
                    .as("use java.time, never java.util.Date");

    @ArchTest
    static final ArchRule NO_STANDARD_STREAM_ACCESS = NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS;

    @ArchTest
    static final ArchRule PACKAGES_FREE_OF_CYCLES =
            SlicesRuleDefinition.slices()
                    .matching("com.codebyte.api.(*)..")
                    .should()
                    .beFreeOfCycles();
}
