package dev.lions.unionflow.server;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;

import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Tests pour UnionFlowServerApplication.
 *
 * <p>Couvre les méthodes privées (via réflexion) et les propriétés injectées.
 * La méthode run() n'est pas testée directement car elle appelle Quarkus.waitForExit()
 * (bloquant). Les méthodes logStartupBanner, logConfiguration, logEndpoints,
 * logArchitecture et buildBaseUrl sont testées via réflexion.
 *
 * @author UnionFlow Team
 */
@QuarkusTest
@DisplayName("UnionFlowServerApplication")
class UnionFlowServerApplicationTest {

    @Inject
    UnionFlowServerApplication application;

    @ConfigProperty(name = "quarkus.application.name", defaultValue = "unionflow-server")
    String applicationName;

    @ConfigProperty(name = "quarkus.application.version", defaultValue = "1.0.0")
    String applicationVersion;

    // =========================================================================
    // Injection & Identity
    // =========================================================================

    @Test
    @DisplayName("Application est injectée et non null")
    void applicationInjected() {
        assertThat(application).isNotNull();
    }

    @Test
    @DisplayName("Instance de QuarkusApplication")
    void isQuarkusApplication() {
        assertThat(application).isInstanceOf(QuarkusApplication.class);
    }

    @Test
    @DisplayName("Instance de UnionFlowServerApplication")
    void isUnionFlowServerApplication() {
        assertThat(application).isInstanceOf(UnionFlowServerApplication.class);
    }

    // =========================================================================
    // Réflexion — présence des méthodes
    // =========================================================================

    @Test
    @DisplayName("main method exists and is callable")
    void mainMethodExists() throws NoSuchMethodException {
        assertThat(UnionFlowServerApplication.class.getMethod("main", String[].class)).isNotNull();
    }

    @Test
    @DisplayName("run method exists and is callable")
    void runMethodExists() throws NoSuchMethodException {
        assertThat(UnionFlowServerApplication.class.getMethod("run", String[].class)).isNotNull();
    }

    // =========================================================================
    // Méthodes privées via réflexion
    // =========================================================================

    @Test
    @DisplayName("logStartupBanner - s'exécute sans exception")
    void logStartupBanner_sansException() throws Exception {
        Method method = UnionFlowServerApplication.class
                .getDeclaredMethod("logStartupBanner");
        method.setAccessible(true);

        assertThatCode(() -> method.invoke(application))
                .doesNotThrowAnyException();
    }

    @Test
    @DisplayName("logConfiguration - s'exécute sans exception")
    void logConfiguration_sansException() throws Exception {
        Method method = UnionFlowServerApplication.class
                .getDeclaredMethod("logConfiguration");
        method.setAccessible(true);

        assertThatCode(() -> method.invoke(application))
                .doesNotThrowAnyException();
    }

    @Test
    @DisplayName("logEndpoints - s'exécute sans exception (profil test)")
    void logEndpoints_sansException() throws Exception {
        Method method = UnionFlowServerApplication.class
                .getDeclaredMethod("logEndpoints");
        method.setAccessible(true);

        assertThatCode(() -> method.invoke(application))
                .doesNotThrowAnyException();
    }

    @Test
    @DisplayName("logArchitecture - s'exécute sans exception")
    void logArchitecture_sansException() throws Exception {
        Method method = UnionFlowServerApplication.class
                .getDeclaredMethod("logArchitecture");
        method.setAccessible(true);

        assertThatCode(() -> method.invoke(application))
                .doesNotThrowAnyException();
    }

    // =========================================================================
    // buildBaseUrl — profils dev/test et prod
    // =========================================================================

    @Test
    @DisplayName("buildBaseUrl - retourne une URL HTTP non-vide")
    void buildBaseUrl_retourneUrlHttp() throws Exception {
        Method method = UnionFlowServerApplication.class
                .getDeclaredMethod("buildBaseUrl");
        method.setAccessible(true);

        String baseUrl = (String) method.invoke(application);

        assertThat(baseUrl).isNotNull();
        assertThat(baseUrl).isNotEmpty();
        assertThat(baseUrl).startsWith("http://");
    }

    @Test
    @DisplayName("buildBaseUrl - retourne une URL non-vide avec port numerique")
    void buildBaseUrl_retourneUrlAvecPort() throws Exception {
        Method method = UnionFlowServerApplication.class
                .getDeclaredMethod("buildBaseUrl");
        method.setAccessible(true);

        String baseUrl = (String) method.invoke(application);

        assertThat(baseUrl).isNotEmpty();
        // doit contenir un caractère ':' suivi d'un ou plusieurs chiffres
        assertThat(baseUrl).containsPattern(":\\d+");
    }

    @Test
    @DisplayName("buildBaseUrl - profil prod avec UNIONFLOW_DOMAIN défini retourne URL https ou URL locale")
    void buildBaseUrl_prodProfileAvecDomain_retourneUrlHttps() throws Exception {
        // Note: Le CDI proxy ne propage pas les modifications de champs au bean sous-jacent.
        // Ce test vérifie que buildBaseUrl() retourne une URL valide (non vide) dans tous les cas.
        // Les branches spécifiques (prod+domain, httpHost personnalisé) sont couvertes par
        // UnionFlowServerApplicationBranchTest et UnionFlowServerApplicationBuildBaseUrlTest
        // qui instancient directement l'application sans CDI.
        Field profileField = UnionFlowServerApplication.class.getDeclaredField("activeProfile");
        profileField.setAccessible(true);
        String originalProfile = (String) profileField.get(application);
        profileField.set(application, "prod");
        try {
            Method method = UnionFlowServerApplication.class.getDeclaredMethod("buildBaseUrl");
            method.setAccessible(true);
            String baseUrl = (String) method.invoke(application);
            // L'URL peut être https:// (si UNIONFLOW_DOMAIN est défini et activeProfile=prod via bean)
            // ou http:// (si le proxy n'a pas propagé la valeur au bean sous-jacent)
            assertThat(baseUrl).isNotNull().isNotEmpty();
            assertThat(baseUrl).satisfiesAnyOf(
                url -> assertThat(url).startsWith("https://"),
                url -> assertThat(url).startsWith("http://")
            );
        } finally {
            profileField.set(application, originalProfile);
        }
    }

    @Test
    @DisplayName("buildBaseUrl - profil prod sans UNIONFLOW_DOMAIN retourne URL http")
    void buildBaseUrl_prodProfileSansDomain_retourneUrlHttp() throws Exception {
        Field profileField = UnionFlowServerApplication.class.getDeclaredField("activeProfile");
        profileField.setAccessible(true);
        String originalProfile = (String) profileField.get(application);
        profileField.set(application, "prod");
        try {
            Method method = UnionFlowServerApplication.class.getDeclaredMethod("buildBaseUrl");
            method.setAccessible(true);
            String baseUrl = (String) method.invoke(application);
            // UNIONFLOW_DOMAIN n'est pas définie en test → branche else → http://localhost:port
            assertThat(baseUrl).isNotNull().isNotEmpty();
        } finally {
            profileField.set(application, originalProfile);
        }
    }

    @Test
    @DisplayName("buildBaseUrl - httpHost personnalisé utilisé directement ou localhost (CDI proxy)")
    void buildBaseUrl_httpHostPersonnalise_utiliseDansUrl() throws Exception {
        // Note: Le CDI proxy ne propage pas les modifications de champs au bean sous-jacent.
        // La branche httpHost != "0.0.0.0" est couverte par UnionFlowServerApplicationBranchTest.
        Field hostField = UnionFlowServerApplication.class.getDeclaredField("httpHost");
        hostField.setAccessible(true);
        String originalHost = (String) hostField.get(application);
        hostField.set(application, "192.168.1.100");
        try {
            Method method = UnionFlowServerApplication.class.getDeclaredMethod("buildBaseUrl");
            method.setAccessible(true);
            String baseUrl = (String) method.invoke(application);
            // L'URL peut contenir "192.168.1.100" (si le proxy a propagé au bean)
            // ou "localhost" (si le proxy ne propage pas la valeur)
            assertThat(baseUrl).isNotNull().isNotEmpty().startsWith("http");
        } finally {
            hostField.set(application, originalHost);
        }
    }

    @Test
    @DisplayName("logEndpoints - profil dev affiche Dev UI et H2 Console sans exception")
    void logEndpoints_devProfile_sansException() throws Exception {
        Field profileField = UnionFlowServerApplication.class.getDeclaredField("activeProfile");
        profileField.setAccessible(true);
        String originalProfile = (String) profileField.get(application);
        profileField.set(application, "dev");
        try {
            Method method = UnionFlowServerApplication.class.getDeclaredMethod("logEndpoints");
            method.setAccessible(true);
            assertThatCode(() -> method.invoke(application)).doesNotThrowAnyException();
        } finally {
            profileField.set(application, originalProfile);
        }
    }

    // =========================================================================
    // Config properties injectées
    // =========================================================================

    @Test
    @DisplayName("Application name est configurée")
    void applicationNameConfigured() {
        assertThat(applicationName).isNotBlank();
    }

    @Test
    @DisplayName("Application version est configurée")
    void applicationVersionConfigured() {
        assertThat(applicationVersion).isNotBlank();
    }

}
