package com.lingniu.ingest.historyapp;

import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor;
import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink;
import com.lingniu.ingest.api.consumer.EnvelopeIngestResult;
import com.lingniu.ingest.eventhistory.EventHistoryEnvelopeIngestor;
import com.lingniu.ingest.eventhistory.Gb32960DecodedFrameService;
import com.lingniu.ingest.eventhistory.Gb32960FrameController;
import com.lingniu.ingest.eventhistory.config.EventHistoryAutoConfiguration;
import com.lingniu.ingest.eventhistory.Jt808LocationHistoryController;
import com.lingniu.ingest.eventhistory.LocationHistoryController;
import com.lingniu.ingest.eventhistory.RawFrameHistoryController;
import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder;
import com.lingniu.ingest.protocol.gb32960.config.Gb32960AutoConfiguration;
import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer;
import com.lingniu.ingest.sink.kafka.KafkaEnvelopeDeadLetterSink;
import com.lingniu.ingest.sink.kafka.KafkaEventSink;
import com.lingniu.ingest.sink.kafka.KafkaEnvelopeConsumerRunner;
import com.lingniu.ingest.sink.kafka.KafkaSinkProperties;
import com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration;
import com.lingniu.ingest.tdenginehistory.TdengineHistorySchema;
import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader;
import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter;
import com.lingniu.ingest.tdenginehistory.config.TdengineHistoryAutoConfiguration;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;

import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

class VehicleHistoryAppCompositionTest {

    @TempDir
    Path tempDir;

    @Test
    void kafkaConsumerConfigurationDoesNotRedeclareServiceOwnedHistoryBeans() {
        List<String> beanMethods = Arrays.stream(VehicleHistoryKafkaConsumerConfiguration.class.getDeclaredMethods())
                .filter(method -> method.isAnnotationPresent(Bean.class))
                .map(Method::getName)
                .toList();

        assertThat(beanMethods)
                .doesNotContain("telemetryEnvelopeRecordMapper")
                .doesNotContain("eventHistoryEnvelopeIngestor")
                .doesNotContain("eventHistoryEnvelopeConsumerProcessor")
                .doesNotContain("eventHistoryRawEnvelopeConsumerProcessor");
    }

    @Test
    void kafkaConsumerRunnerRejectsGenericHistoryBindings() {
        new ApplicationContextRunner()
                .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class)
                .withBean("eventHistoryEnvelopeConsumerProcessor", EnvelopeConsumerProcessor.class,
                        () -> processor("event-history"))
                .withBean("eventHistoryRawEnvelopeConsumerProcessor", EnvelopeConsumerProcessor.class,
                        () -> processor("event-history-raw"))
                .withBean(KafkaSinkProperties.class, VehicleHistoryAppCompositionTest::genericOnlyHistoryConsumerProps)
                .withPropertyValues("lingniu.ingest.sink.kafka.consumer.enabled=true")
                .run(context -> {
                    assertThat(context).hasFailed();
                    assertThat(context.getStartupFailure())
                            .hasRootCauseMessage("no vehicle history kafka consumer workers created; check consumer bindings");
                });
    }

    @Test
    void createsTdengineHistoryStorageAndQueryBeansWithoutGb32960TcpServerOrLocalArchiveSink() {
        new ApplicationContextRunner()
                .withConfiguration(AutoConfigurations.of(
                        TdengineHistoryAutoConfiguration.class,
                        KafkaSinkAutoConfiguration.class,
                        Gb32960AutoConfiguration.class,
                        EventHistoryAutoConfiguration.class))
                .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class)
                .withAllowBeanDefinitionOverriding(true)
                .withBean("kafkaProducer", KafkaProducer.class, VehicleHistoryAppCompositionTest::kafkaProducer)
                .withPropertyValues(
                        "lingniu.ingest.tdengine-history.enabled=true",
                        "lingniu.ingest.tdengine-history.database=vehicle_history_test",
                        "lingniu.ingest.event-history.enabled=true",
                        "lingniu.ingest.gb32960.enabled=true",
                        "lingniu.ingest.gb32960.server.enabled=false",
                        "lingniu.ingest.sink.kafka.enabled=true",
                        "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092",
                        "lingniu.ingest.sink.kafka.consumer.enabled=false",
                        "lingniu.ingest.vehicle-state.enabled=false",
                        "lingniu.ingest.vehicle-stat.enabled=false")
                .run(context -> {
                    assertThat(context).doesNotHaveBean("archiveStore");
                    assertThat(context).doesNotHaveBean("rawArchiveEventSink");
                    assertThat(context).doesNotHaveBean("eventFileStore");
                    assertThat(context).doesNotHaveBean("eventFileStoreSink");
                    assertThat(context).hasSingleBean(TdengineHistorySchema.class);
                    assertThat(context).hasSingleBean(KafkaEventSink.class);
                    assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class);

                    assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class);
                    assertThat(context).hasSingleBean(Gb32960MessageDecoder.class);
                    assertThat(context).hasSingleBean(Gb32960DecodedFrameService.class);
                    assertThat(context).hasSingleBean(LocationHistoryController.class);
                    assertThat(context).hasSingleBean(RawFrameHistoryController.class);
                    assertThat(context).doesNotHaveBean("telemetryFieldHistoryController");
                    assertThat(context).doesNotHaveBean(Gb32960FrameController.class);
                    assertThat(context).doesNotHaveBean(Jt808LocationHistoryController.class);
                    assertThat(context).doesNotHaveBean(Gb32960NettyServer.class);
                });
    }

    @Test
    void createsHistoryIngestorForTdengineOnlyRuntime() {
        new ApplicationContextRunner()
                .withConfiguration(AutoConfigurations.of(EventHistoryAutoConfiguration.class))
                .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class)
                .withBean(TdengineHistoryWriter.class, () -> mock(TdengineHistoryWriter.class))
                .withBean(EnvelopeDeadLetterSink.class, () -> mock(EnvelopeDeadLetterSink.class))
                .withPropertyValues(
                        "lingniu.ingest.event-history.enabled=true",
                        "lingniu.ingest.sink.kafka.consumer.enabled=false")
                .run(context -> {
                    assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class);
                    assertThat(context).doesNotHaveBean("eventFileStore");
                });
    }

    @Test
    void applicationScanDoesNotExposeTelemetryFieldControllerByDefault() {
        new ApplicationContextRunner()
                .withUserConfiguration(VehicleHistoryApplication.class)
                .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class))
                .withPropertyValues(
                        "spring.cloud.nacos.config.enabled=false",
                        "spring.config.import=",
                        "lingniu.ingest.event-history.enabled=true",
                        "lingniu.ingest.tdengine-history.enabled=false",
                        "lingniu.ingest.sink.kafka.enabled=false",
                        "lingniu.ingest.sink.kafka.consumer.enabled=false",
                        "lingniu.ingest.gb32960.enabled=false")
                .run(context -> {
                    assertThat(context).hasSingleBean(LocationHistoryController.class);
                    assertThat(context).hasSingleBean(RawFrameHistoryController.class);
                    assertThat(context).doesNotHaveBean("telemetryFieldHistoryController");
                });
    }

    @Test
    void createsKafkaConsumerRunnerWhenTdengineHistoryRuntimeConsumesKafka() {
        new ApplicationContextRunner()
                .withConfiguration(AutoConfigurations.of(
                        TdengineHistoryAutoConfiguration.class,
                        KafkaSinkAutoConfiguration.class,
                        EventHistoryAutoConfiguration.class))
                .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class)
                .withAllowBeanDefinitionOverriding(true)
                .withBean("kafkaProducer", KafkaProducer.class, VehicleHistoryAppCompositionTest::kafkaProducer)
                .withPropertyValues(
                        "lingniu.ingest.tdengine-history.enabled=true",
                        "lingniu.ingest.tdengine-history.database=vehicle_history_test",
                        "lingniu.ingest.event-history.enabled=true",
                        "lingniu.ingest.sink.kafka.enabled=true",
                        "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092",
                        "lingniu.ingest.sink.kafka.consumer.enabled=true",
                        "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.enabled=true",
                        "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.group-id=history-gb32960",
                        "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.topics[0]=vehicle.event.gb32960.v1")
                .run(context -> {
                    assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class);
                    assertThat(context.getBeanNamesForType(EnvelopeConsumerProcessor.class))
                            .contains("eventHistoryEnvelopeConsumerProcessor");
                    assertThat(context).hasSingleBean(KafkaEnvelopeConsumerRunner.class);
                });
    }

    @SuppressWarnings("unchecked")
    private static KafkaProducer<String, byte[]> kafkaProducer() {
        return mock(KafkaProducer.class);
    }

    private static EnvelopeConsumerProcessor processor(String service) {
        return new EnvelopeConsumerProcessor(
                service,
                ignored -> EnvelopeIngestResult.processed("event-1", "VIN001"),
                ignored -> {});
    }

    private static KafkaSinkProperties genericOnlyHistoryConsumerProps() {
        KafkaSinkProperties props = new KafkaSinkProperties();
        props.setBootstrapServers("localhost:9092");
        props.getConsumer().setEnabled(true);
        props.getConsumer().setBindings(Map.of(
                "eventHistoryEnvelopeConsumerProcessor", binding("history-event", "vehicle.event.generic.v1"),
                "eventHistoryRawEnvelopeConsumerProcessor", binding("history-raw", "vehicle.raw.generic.v1")));
        return props;
    }

    private static KafkaSinkProperties.Binding binding(String groupId, String topic) {
        KafkaSinkProperties.Binding binding = new KafkaSinkProperties.Binding();
        binding.setGroupId(groupId);
        binding.setTopics(List.of(topic));
        return binding;
    }

}
