package com.cameleer.server.app.search;

import com.cameleer.server.core.ingestion.BufferedLogEntry;
import com.cameleer.server.core.search.LogSearchRequest;
import com.cameleer.server.core.search.LogSearchResponse;
import com.cameleer.common.model.LogEntry;
import com.cameleer.server.app.ClickHouseTestHelper;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.testcontainers.clickhouse.ClickHouseContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.time.Instant;
import java.util.List;

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

/**
 * Integration test for the {@code instanceIds} multi-value filter on
 * {@link ClickHouseLogStore#search(LogSearchRequest)}.
 *
 * <p>Three rows are seeded with distinct {@code instance_id} values:
 * <ul>
 *   <li>{@code prod-app1-0-aaa11111} — included in filter</li>
 *   <li>{@code prod-app1-1-aaa11111} — included in filter</li>
 *   <li>{@code prod-app1-0-bbb22222} — excluded from filter</li>
 * </ul>
 */
@Testcontainers
class ClickHouseLogStoreInstanceIdsIT {

    @Container
    static final ClickHouseContainer clickhouse =
            new ClickHouseContainer("clickhouse/clickhouse-server:24.12");

    private JdbcTemplate jdbc;
    private ClickHouseLogStore store;

    private static final String TENANT  = "default";
    private static final String ENV     = "prod";
    private static final String APP     = "app1";
    private static final String INST_A  = "prod-app1-0-aaa11111";
    private static final String INST_B  = "prod-app1-1-aaa11111";
    private static final String INST_C  = "prod-app1-0-bbb22222";

    @BeforeEach
    void setUp() throws Exception {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(clickhouse.getJdbcUrl());
        ds.setUsername(clickhouse.getUsername());
        ds.setPassword(clickhouse.getPassword());

        jdbc  = new JdbcTemplate(ds);
        ClickHouseTestHelper.executeInitSql(jdbc);
        jdbc.execute("TRUNCATE TABLE logs");

        store = new ClickHouseLogStore(TENANT, jdbc);

        Instant base = Instant.parse("2026-04-23T09:00:00Z");
        seedLog(INST_A, base,               "msg-from-replica-0-gen-aaa");
        seedLog(INST_B, base.plusSeconds(1), "msg-from-replica-1-gen-aaa");
        seedLog(INST_C, base.plusSeconds(2), "msg-from-replica-0-gen-bbb");
    }

    @AfterEach
    void tearDown() {
        jdbc.execute("TRUNCATE TABLE logs");
    }

    private void seedLog(String instanceId, Instant ts, String message) {
        LogEntry entry = new LogEntry(ts, "INFO", "com.example.Svc", message, "main", null, null);
        store.insertBufferedBatch(List.of(
                new BufferedLogEntry(TENANT, ENV, instanceId, APP, entry)));
    }

    // ── Tests ─────────────────────────────────────────────────────────────

    @Test
    void search_instanceIds_returnsOnlyMatchingInstances() {
        LogSearchResponse result = store.search(new LogSearchRequest(
                null,
                List.of(),
                APP,
                null,
                null,
                null,
                ENV,
                List.of(),
                null,
                null,
                null,
                100,
                "desc",
                List.of(INST_A, INST_B)));

        assertThat(result.data()).hasSize(2);
        assertThat(result.data())
                .extracting(r -> r.instanceId())
                .containsExactlyInAnyOrder(INST_A, INST_B);
        assertThat(result.data())
                .extracting(r -> r.instanceId())
                .doesNotContain(INST_C);
    }

    @Test
    void search_emptyInstanceIds_returnsAllRows() {
        LogSearchResponse result = store.search(new LogSearchRequest(
                null,
                List.of(),
                APP,
                null,
                null,
                null,
                ENV,
                List.of(),
                null,
                null,
                null,
                100,
                "desc",
                List.of()));

        assertThat(result.data()).hasSize(3);
    }

    @Test
    void search_nullInstanceIds_returnsAllRows() {
        LogSearchResponse result = store.search(new LogSearchRequest(
                null,
                List.of(),
                APP,
                null,
                null,
                null,
                ENV,
                List.of(),
                null,
                null,
                null,
                100,
                "desc",
                null));

        assertThat(result.data()).hasSize(3);
    }

    @Test
    void search_instanceIds_singleValue_filtersToOneReplica() {
        LogSearchResponse result = store.search(new LogSearchRequest(
                null,
                List.of(),
                APP,
                null,
                null,
                null,
                ENV,
                List.of(),
                null,
                null,
                null,
                100,
                "desc",
                List.of(INST_C)));

        assertThat(result.data()).hasSize(1);
        assertThat(result.data().get(0).instanceId()).isEqualTo(INST_C);
        assertThat(result.data().get(0).message()).isEqualTo("msg-from-replica-0-gen-bbb");
    }

    @Test
    void search_instanceIds_doesNotConflictWithSingularInstanceId() {
        // Singular instanceId=INST_A AND instanceIds=[INST_B] → intersection = empty
        // (both conditions apply: instance_id = A AND instance_id IN (B))
        LogSearchResponse result = store.search(new LogSearchRequest(
                null,
                List.of(),
                APP,
                INST_A,         // singular
                null,
                null,
                ENV,
                List.of(),
                null,
                null,
                null,
                100,
                "desc",
                List.of(INST_B)));  // plural — no overlap

        assertThat(result.data()).isEmpty();
    }
}
