package dev.lions.unionflow.server.service;

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

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
 * Tests unitaires purs pour {@link WaveCheckoutService} — branche HTTP réelle.
 *
 * <p>Pas de {@code @QuarkusTest} : le service est instancié directement pour éviter tout
 * redémarrage Quarkus (les profils/restarts multiples causent un NPE dans PathTestHelper).
 *
 * <p>Couvre toutes les branches de {@code createSession} et {@code getRedirectBaseUrl}.
 */
class WaveCheckoutServiceRealPathTest {

    static final int MOCK_PORT = 19876;

    enum ResponseMode { SUCCESS, HTTP_ERROR, INVALID_JSON, ID_ONLY_NO_URL }

    private static final AtomicReference<ResponseMode> MODE =
            new AtomicReference<>(ResponseMode.SUCCESS);

    private static HttpServer mockServer;

    private WaveCheckoutService service;

    @BeforeAll
    static void startMockServer() throws IOException {
        mockServer = HttpServer.create(new InetSocketAddress(MOCK_PORT), 0);
        mockServer.createContext("/v1/checkout/sessions", exchange -> {
            exchange.getRequestBody().readAllBytes();
            ResponseMode current = MODE.get();
            switch (current) {
                case HTTP_ERROR -> {
                    byte[] body = "{\"error\":\"Bad Request\"}".getBytes(StandardCharsets.UTF_8);
                    exchange.sendResponseHeaders(400, body.length);
                    try (OutputStream os = exchange.getResponseBody()) { os.write(body); }
                }
                case INVALID_JSON -> {
                    byte[] body = "{}".getBytes(StandardCharsets.UTF_8);
                    exchange.getResponseHeaders().set("Content-Type", "application/json");
                    exchange.sendResponseHeaders(200, body.length);
                    try (OutputStream os = exchange.getResponseBody()) { os.write(body); }
                }
                case ID_ONLY_NO_URL -> {
                    // JSON avec "id" mais sans "wave_launch_url" → L106: id != null (A=false), waveLaunchUrl == null (B=true) → throw
                    byte[] body = "{\"id\":\"cos-id-only-001\"}".getBytes(StandardCharsets.UTF_8);
                    exchange.getResponseHeaders().set("Content-Type", "application/json");
                    exchange.sendResponseHeaders(200, body.length);
                    try (OutputStream os = exchange.getResponseBody()) { os.write(body); }
                }
                default -> {
                    String json = "{\"id\":\"cos-real-0001\","
                            + "\"wave_launch_url\":\"https://pay.wave.com/m/cos-real-0001\"}";
                    byte[] body = json.getBytes(StandardCharsets.UTF_8);
                    exchange.getResponseHeaders().set("Content-Type", "application/json");
                    exchange.sendResponseHeaders(200, body.length);
                    try (OutputStream os = exchange.getResponseBody()) { os.write(body); }
                }
            }
        });
        mockServer.start();
    }

    @AfterAll
    static void stopMockServer() {
        if (mockServer != null) {
            mockServer.stop(0);
        }
    }

    @BeforeEach
    void setUp() {
        service = new WaveCheckoutService();
        service.apiKey = "test-real-api-key";
        // URL avec slash final → couvre la branche baseUrl.endsWith("/") == true (ligne 70)
        service.baseUrl = "http://localhost:" + MOCK_PORT + "/v1/";
        service.signingSecret = "test-signing-secret";
        service.redirectBaseUrl = "http://localhost:8080";
        service.mockEnabled = false;
        MODE.set(ResponseMode.SUCCESS);
    }

    @AfterEach
    void resetMode() {
        MODE.set(ResponseMode.SUCCESS);
    }

    // -------------------------------------------------------------------------
    // Branche SUCCESS — baseUrl se termine par "/" (couvre ligne 70 branche true)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession en mode réel retourne id et wave_launch_url valides")
    void createSession_realMode_success_returnsValidResponse()
            throws WaveCheckoutService.WaveCheckoutException {
        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "10000", "XOF",
                "https://success.example.com/pay",
                "https://error.example.com/pay",
                "REF-REAL-001", null);

        assertThat(response).isNotNull();
        assertThat(response.id).isEqualTo("cos-real-0001");
        assertThat(response.waveLaunchUrl).isEqualTo("https://pay.wave.com/m/cos-real-0001");
    }

    // -------------------------------------------------------------------------
    // Branche baseUrl sans "/v1" → !base.endsWith("/v1") == true (ligne 71 branche true)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession avec baseUrl sans /v1 ajoute /v1 automatiquement")
    void createSession_realMode_baseUrlWithoutV1_appendsV1()
            throws WaveCheckoutService.WaveCheckoutException {
        // baseUrl ne se termine PAS par "/" et ne contient pas "/v1"
        // → baseUrl.endsWith("/") == false → base = baseUrl (ligne 70 branche false)
        // → !base.endsWith("/v1") == true → base = base + "/v1" (ligne 71 corps exécuté)
        service.baseUrl = "http://localhost:" + MOCK_PORT;

        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "5000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-NO-V1", null);

        assertThat(response.id).isEqualTo("cos-real-0001");
    }

    @Test
    @DisplayName("createSession avec clientRef > 255 chars tronque la référence")
    void createSession_realMode_longClientRef_truncatesRef()
            throws WaveCheckoutService.WaveCheckoutException {
        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "5000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "X".repeat(300), null);

        assertThat(response.id).isEqualTo("cos-real-0001");
    }

    @Test
    @DisplayName("createSession avec restrictMobile inclut le numéro dans le body")
    void createSession_realMode_withRestrictMobile()
            throws WaveCheckoutService.WaveCheckoutException {
        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "7500", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-MOBILE", "+22170000001");

        assertThat(response.id).isEqualTo("cos-real-0001");
    }

    @Test
    @DisplayName("createSession sans clientRef ni restrictMobile fonctionne")
    void createSession_realMode_noOptionalFields()
            throws WaveCheckoutService.WaveCheckoutException {
        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "2500", "XOF",
                "https://s.example.com", "https://e.example.com",
                null, null);

        assertThat(response.id).isNotNull();
    }

    @Test
    @DisplayName("createSession avec currency null utilise XOF par défaut")
    void createSession_realMode_nullCurrency_usesXof()
            throws WaveCheckoutService.WaveCheckoutException {
        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "1000", null,
                "https://s.example.com", "https://e.example.com",
                "REF-NULL-CURRENCY", null);

        assertThat(response).isNotNull();
    }

    @Test
    @DisplayName("createSession sans signingSecret ne calcule pas la signature Wave")
    void createSession_realMode_noSigningSecret()
            throws WaveCheckoutService.WaveCheckoutException {
        service.signingSecret = ""; // → waveSignature = null → pas de header Wave-Signature
        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "3000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-NO-SIG", null);

        assertThat(response.id).isEqualTo("cos-real-0001");
    }

    // -------------------------------------------------------------------------
    // Branche HTTP 4xx → WaveCheckoutException
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession lève WaveCheckoutException si l'API retourne HTTP 4xx")
    void createSession_realMode_httpError_throwsWaveCheckoutException() {
        MODE.set(ResponseMode.HTTP_ERROR);

        assertThatThrownBy(() -> service.createSession(
                "10000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-ERR", null))
                .isInstanceOf(WaveCheckoutService.WaveCheckoutException.class)
                .hasMessageContaining("400");
    }

    // -------------------------------------------------------------------------
    // Branche JSON invalide → WaveCheckoutException (lignes 106-108)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession lève WaveCheckoutException si la réponse JSON est invalide")
    void createSession_realMode_invalidJson_throwsWaveCheckoutException() {
        MODE.set(ResponseMode.INVALID_JSON);

        assertThatThrownBy(() -> service.createSession(
                "10000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-INVALID", null))
                .isInstanceOf(WaveCheckoutService.WaveCheckoutException.class)
                .hasMessageContaining("invalide");
    }

    // -------------------------------------------------------------------------
    // Branche catch(Exception e) — lignes 112-114
    // Hôte invalide → ConnectException → catch(Exception e) → WaveCheckoutException
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession lève WaveCheckoutException si la connexion échoue (catch Exception)")
    void createSession_realMode_connectionFailed_catchesGenericException() {
        // Port fermé sur localhost → ConnectException
        // → catch(Exception e) lignes 112-114
        service.baseUrl = "http://localhost:1";

        assertThatThrownBy(() -> service.createSession(
                "1000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-CONN-FAIL", null))
                .isInstanceOf(WaveCheckoutService.WaveCheckoutException.class)
                .hasMessageContaining("Erreur appel Wave Checkout");
    }

    // -------------------------------------------------------------------------
    // escapeJson — caractères spéciaux
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession avec clientRef contenant des caractères spéciaux JSON")
    void createSession_realMode_clientRefWithSpecialChars()
            throws WaveCheckoutService.WaveCheckoutException {
        String specialRef = "ref\\with\"quotes\nand\rnewlines";

        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "500", "XOF",
                "https://s.example.com", "https://e.example.com",
                specialRef, null);

        assertThat(response).isNotNull();
    }

    // -------------------------------------------------------------------------
    // getRedirectBaseUrl — ligne 153 (branche null/blank)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("getRedirectBaseUrl retourne la valeur configurée si non vide")
    void getRedirectBaseUrl_nonBlank_returnsConfiguredValue() {
        service.redirectBaseUrl = "http://example.com";
        assertThat(service.getRedirectBaseUrl()).isEqualTo("http://example.com");
    }

    @Test
    @DisplayName("getRedirectBaseUrl retourne localhost:8080 si redirectBaseUrl est null")
    void getRedirectBaseUrl_null_returnsDefault() {
        service.redirectBaseUrl = null;
        assertThat(service.getRedirectBaseUrl()).isEqualTo("http://localhost:8080");
    }

    @Test
    @DisplayName("getRedirectBaseUrl retourne localhost:8080 si redirectBaseUrl est blank")
    void getRedirectBaseUrl_blank_returnsDefault() {
        service.redirectBaseUrl = "   ";
        assertThat(service.getRedirectBaseUrl()).isEqualTo("http://localhost:8080");
    }

    // -------------------------------------------------------------------------
    // L64 : useMock = mockEnabled || apiKey == null → apiKey null avec mockEnabled=false
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession avec apiKey null → useMock=true via apiKey==null (branche L64)")
    void createSession_apiKeyNull_useMockTrue()
            throws WaveCheckoutService.WaveCheckoutException {
        service.apiKey = null; // apiKey == null → true → useMock = true
        service.mockEnabled = false;

        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "5000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-NULL-KEY", null);

        // useMock=true → mode mock
        assertThat(response).isNotNull();
        assertThat(response.id).startsWith("cos-mock-");
    }

    // -------------------------------------------------------------------------
    // L70 : baseUrl == null → replaceAll sur null → NullPointerException → ou branche true
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession avec baseUrl null → NullPointerException (branche L70 baseUrl==null, avant try-catch)")
    void createSession_baseUrlNull_handledGracefully() {
        // baseUrl=null, mockEnabled=false, apiKey valide
        // Ternaire L70: (null == null) = true → exécute null.replaceAll() → NPE
        // NPE survient AVANT le bloc try{}, donc non attrapée en WaveCheckoutException
        service.baseUrl = null;
        service.mockEnabled = false;
        service.apiKey = "valid-api-key";

        assertThatThrownBy(() -> service.createSession(
                "5000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-NULL-URL", null))
                .isInstanceOf(NullPointerException.class);
    }

    // -------------------------------------------------------------------------
    // L78 : signingSecret null → condition false → pas de signature (branche L78 signingSecret==null)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession avec signingSecret null → pas de signature Wave (branche L78 null)")
    void createSession_signingSecretNull_noSignature()
            throws WaveCheckoutService.WaveCheckoutException {
        service.signingSecret = null; // null → condition `signingSecret != null && !isBlank()` = false
        service.mockEnabled = false;
        service.apiKey = "valid-key";
        service.baseUrl = "http://localhost:" + MOCK_PORT + "/";

        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "3000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-SIG-NULL", null);

        assertThat(response.id).isEqualTo("cos-real-0001");
    }

    // -------------------------------------------------------------------------
    // L64 : useMock = mockEnabled || apiKey == null || apiKey.trim().isBlank()
    // Branche C=true : apiKey non-null mais blank → useMock=true (manquant)
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession avec apiKey blank (espaces) → useMock=true via apiKey.trim().isBlank() (branche L64 C=true)")
    void createSession_apiKeyBlank_useMockTrue()
            throws WaveCheckoutService.WaveCheckoutException {
        service.apiKey = "   "; // non-null mais blank → apiKey == null = false, trim().isBlank() = true → useMock = true
        service.mockEnabled = false;

        WaveCheckoutService.WaveCheckoutSessionResponse response = service.createSession(
                "5000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-BLANK-KEY", null);

        // useMock=true → mode mock (id commence par "cos-mock-")
        assertThat(response).isNotNull();
        assertThat(response.id).startsWith("cos-mock-");
    }

    // -------------------------------------------------------------------------
    // L106 : id != null (A=false) mais waveLaunchUrl == null (B=true) → throw
    // -------------------------------------------------------------------------

    @Test
    @DisplayName("createSession lève WaveCheckoutException si wave_launch_url manquant (L106 A=false, B=true)")
    void createSession_idPresentButNoWaveLaunchUrl_throwsWaveCheckoutException() {
        MODE.set(ResponseMode.ID_ONLY_NO_URL);
        // Serveur retourne {"id":"cos-id-only-001"} sans "wave_launch_url"
        // → id != null (A=false) → évalue B : waveLaunchUrl == null (B=true) → throw

        assertThatThrownBy(() -> service.createSession(
                "10000", "XOF",
                "https://s.example.com", "https://e.example.com",
                "REF-NO-URL", null))
                .isInstanceOf(WaveCheckoutService.WaveCheckoutException.class)
                .hasMessageContaining("invalide");
    }
}
