package com.cameleer.server.app.search;

import com.cameleer.server.app.ClickHouseTestHelper;
import com.cameleer.server.app.storage.ClickHouseExecutionStore;
import com.cameleer.server.core.alerting.AlertMatchSpec;
import com.cameleer.server.core.ingestion.MergedExecution;
import com.zaxxer.hikari.HikariDataSource;
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 java.util.Map;

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

@Testcontainers
class ClickHouseSearchIndexAlertingCountIT {

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

    private JdbcTemplate jdbc;
    private ClickHouseSearchIndex searchIndex;
    private ClickHouseExecutionStore store;

    @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 executions");
        jdbc.execute("TRUNCATE TABLE processor_executions");

        store = new ClickHouseExecutionStore("default", jdbc);
        searchIndex = new ClickHouseSearchIndex("default", jdbc);
    }

    private MergedExecution exec(String id, String status, String appId, String routeId, String attributes, Instant start) {
        return new MergedExecution(
                "default", 1L, id, routeId, "agent-1", appId, "prod",
                status, "", "exchange-" + id,
                start, start.plusMillis(100), 100L,
                "", "", "", "", "", "",      // errorMessage..rootCauseMessage
                "", "FULL",                 // diagramContentHash, engineLevel
                "", "", "", "", "", "",     // inputBody, outputBody, inputHeaders, outputHeaders, inputProperties, outputProperties
                attributes,                // attributes (JSON string)
                "", "",                    // traceId, spanId
                false, false,
                null, null
        );
    }

    @Test
    void countExecutionsForAlerting_byStatus() {
        Instant base = Instant.parse("2026-04-19T10:00:00Z");
        store.insertExecutionBatch(List.of(
                exec("e1", "FAILED",    "orders", "route-a", "{}", base),
                exec("e2", "FAILED",    "orders", "route-a", "{}", base.plusSeconds(1)),
                exec("e3", "COMPLETED", "orders", "route-a", "{}", base.plusSeconds(2))
        ));

        AlertMatchSpec spec = new AlertMatchSpec(
                "default", "prod", "orders", null, "FAILED",
                null,
                base.minusSeconds(10), base.plusSeconds(60), null);

        assertThat(searchIndex.countExecutionsForAlerting(spec)).isEqualTo(2);
    }

    @Test
    void countExecutionsForAlerting_byRouteId() {
        Instant base = Instant.parse("2026-04-19T10:00:00Z");
        store.insertExecutionBatch(List.of(
                exec("e1", "FAILED", "orders", "route-a", "{}", base),
                exec("e2", "FAILED", "orders", "route-b", "{}", base.plusSeconds(1)),
                exec("e3", "FAILED", "orders", "route-a", "{}", base.plusSeconds(2))
        ));

        AlertMatchSpec spec = new AlertMatchSpec(
                "default", "prod", null, "route-a", null,
                null,
                base.minusSeconds(10), base.plusSeconds(60), null);

        assertThat(searchIndex.countExecutionsForAlerting(spec)).isEqualTo(2);
    }

    @Test
    void countExecutionsForAlerting_withAttributes() {
        Instant base = Instant.parse("2026-04-19T10:00:00Z");
        store.insertExecutionBatch(List.of(
                exec("e1", "FAILED", "orders", "route-a", "{\"region\":\"eu\",\"priority\":\"high\"}", base),
                exec("e2", "FAILED", "orders", "route-a", "{\"region\":\"us\"}", base.plusSeconds(1)),
                exec("e3", "FAILED", "orders", "route-a", "{}", base.plusSeconds(2))
        ));

        AlertMatchSpec spec = new AlertMatchSpec(
                "default", "prod", null, null, null,
                Map.of("region", "eu"),
                base.minusSeconds(10), base.plusSeconds(60), null);

        assertThat(searchIndex.countExecutionsForAlerting(spec)).isEqualTo(1);
    }

    @Test
    void countExecutionsForAlerting_afterCursor() {
        Instant base = Instant.parse("2026-04-19T10:00:00Z");
        store.insertExecutionBatch(List.of(
                exec("e1", "FAILED", "orders", "route-a", "{}", base),
                exec("e2", "FAILED", "orders", "route-a", "{}", base.plusSeconds(5)),
                exec("e3", "FAILED", "orders", "route-a", "{}", base.plusSeconds(10))
        ));

        // after = base+2s, so only e2 and e3 should count
        AlertMatchSpec spec = new AlertMatchSpec(
                "default", "prod", null, null, null,
                null,
                base.minusSeconds(1), base.plusSeconds(60), base.plusSeconds(2));

        assertThat(searchIndex.countExecutionsForAlerting(spec)).isEqualTo(2);
    }

    @Test
    void countExecutionsForAlerting_noMatchReturnsZero() {
        Instant base = Instant.parse("2026-04-19T10:00:00Z");
        store.insertExecutionBatch(List.of(
                exec("e1", "COMPLETED", "orders", "route-a", "{}", base)
        ));

        AlertMatchSpec spec = new AlertMatchSpec(
                "default", "prod", null, null, "FAILED",
                null,
                base.minusSeconds(10), base.plusSeconds(60), null);

        assertThat(searchIndex.countExecutionsForAlerting(spec)).isZero();
    }
}
