package dev.lions.unionflow.server.messaging;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.mockito.ArgumentMatchers;
import io.smallrye.reactive.messaging.kafka.Record;
import org.eclipse.microprofile.reactive.messaging.Emitter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Tests de couverture pour la méthode privée {@code KafkaEventProducer.publishToChannel}.
 *
 * <p>Utilise l'instanciation directe + réflexion pour éviter les limitations de CDI
 * ({@code @InjectMock} ne supporte pas les beans {@code @Singleton} comme ObjectMapper).
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("KafkaEventProducer - publishToChannel branches catch")
class KafkaEventProducerPublishToChannelTest {

    private KafkaEventProducer producer;
    private ObjectMapper mockObjectMapper;

    @BeforeEach
    @SuppressWarnings("unchecked")
    void setup() throws Exception {
        producer = new KafkaEventProducer();
        mockObjectMapper = mock(ObjectMapper.class);

        // Injecter le mock ObjectMapper dans l'instance directe
        Field omField = KafkaEventProducer.class.getDeclaredField("objectMapper");
        omField.setAccessible(true);
        omField.set(producer, mockObjectMapper);
    }

    /** Invoque publishToChannel via réflexion sur l'instance directe avec un mock Emitter. */
    @SuppressWarnings("unchecked")
    private void invokePublishToChannel(Emitter<Record<String, String>> emitter,
                                         String key, Map<String, Object> event,
                                         String topicName) throws Exception {
        Method method = KafkaEventProducer.class.getDeclaredMethod(
                "publishToChannel", Emitter.class, String.class, Map.class, String.class);
        method.setAccessible(true);
        method.invoke(producer, emitter, key, event, topicName);
    }

    // =========================================================================
    // catch (JsonProcessingException e) — lignes 149-150
    // =========================================================================

    @Test
    @DisplayName("publishToChannel - JsonProcessingException → loggée sans relancer (branche catch L149-150)")
    void publishToChannel_jsonProcessingException_loggeeEtNonPropagee() throws Exception {
        when(mockObjectMapper.writeValueAsString(any()))
                .thenThrow(new JsonProcessingException("Erreur sérialisation simulée") {});

        @SuppressWarnings("unchecked")
        Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);

        assertThatCode(() -> invokePublishToChannel(mockEmitter, "key", new HashMap<>(), "finance-approvals"))
                .doesNotThrowAnyException();
    }

    @Test
    @DisplayName("publishToChannel - JsonProcessingException sur topic notifications → loggée sans relancer")
    void publishToChannel_jsonProcessingException_topicNotifications() throws Exception {
        when(mockObjectMapper.writeValueAsString(any()))
                .thenThrow(new JsonProcessingException("Serialisation notification échouée") {});

        @SuppressWarnings("unchecked")
        Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);

        assertThatCode(() -> invokePublishToChannel(mockEmitter, "user-id", new HashMap<>(), "notifications"))
                .doesNotThrowAnyException();
    }

    // =========================================================================
    // catch (Exception e) générique — lignes 151-152
    // =========================================================================

    @Test
    @DisplayName("publishToChannel - RuntimeException depuis emitter → loggée sans relancer (branche catch L151-152)")
    void publishToChannel_exceptionGenerique_loggeeEtNonPropagee() throws Exception {
        when(mockObjectMapper.writeValueAsString(any())).thenReturn("{\"type\":\"test\"}");

        @SuppressWarnings("unchecked")
        Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);
        doThrow(new RuntimeException("Erreur émetteur simulée")).when(mockEmitter).send(ArgumentMatchers.<Record<String, String>>any());

        assertThatCode(() -> invokePublishToChannel(mockEmitter, "key", new HashMap<>(), "dashboard-stats"))
                .doesNotThrowAnyException();
    }

    @Test
    @DisplayName("publishToChannel - IllegalStateException depuis emitter → loggée sans relancer")
    void publishToChannel_exceptionGenerique_topicMembersEvents() throws Exception {
        when(mockObjectMapper.writeValueAsString(any())).thenReturn("{\"type\":\"member\"}");

        @SuppressWarnings("unchecked")
        Emitter<Record<String, String>> mockEmitter = mock(Emitter.class);
        doThrow(new IllegalStateException("État emitter invalide")).when(mockEmitter).send(ArgumentMatchers.<Record<String, String>>any());

        assertThatCode(() -> invokePublishToChannel(mockEmitter, "member-id", new HashMap<>(), "members-events"))
                .doesNotThrowAnyException();
    }
}
