package com.cameleer.server.app.storage;

import com.cameleer.server.core.ingestion.MergedExecution;
import com.cameleer.common.model.ExecutionStatus;
import com.cameleer.common.model.FlatProcessorRecord;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.cameleer.server.app.ClickHouseTestHelper;
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.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.List;
import java.util.Map;

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

@Testcontainers
class ClickHouseExecutionStoreIT {

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

    private JdbcTemplate jdbc;
    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);
    }

    @Test
    void insertExecutionBatch_writesToClickHouse() {
        MergedExecution exec = new MergedExecution(
                "default", 1L, "exec-1", "route-a", "agent-1", "my-app", "default",
                "COMPLETED", "corr-1", "exchange-1",
                Instant.parse("2026-03-31T10:00:00Z"),
                Instant.parse("2026-03-31T10:00:01Z"),
                1000L,
                "some error", "stack trace", "IOException", "IO",
                "FileNotFoundException", "file not found",
                "hash-abc", "FULL",
                "{\"key\":\"val\"}", "{\"out\":\"val\"}",
                "{\"h1\":\"v1\"}", "{\"h2\":\"v2\"}",
                "", "",
                "{\"attr\":\"val\"}",
                "trace-123", "span-456",
                true, false,
                null, null
        );

        store.insertExecutionBatch(List.of(exec));

        Integer count = jdbc.queryForObject(
                "SELECT count() FROM executions WHERE execution_id = 'exec-1'",
                Integer.class);
        assertThat(count).isEqualTo(1);
    }

    @Test
    void insertProcessorBatch_writesToClickHouse() {
        FlatProcessorRecord proc = new FlatProcessorRecord(1, "proc-1", "to");
        proc.setStatus(ExecutionStatus.COMPLETED);
        proc.setStartTime(Instant.parse("2026-03-31T10:00:00Z"));
        proc.setDurationMs(50L);
        proc.setResolvedEndpointUri("http://example.com");
        proc.setInputBody("input body");
        proc.setOutputBody("output body");
        proc.setInputHeaders(Map.of("h1", "v1"));
        proc.setOutputHeaders(Map.of("h2", "v2"));
        proc.setAttributes(Map.of("a1", "v1"));

        store.insertProcessorBatch(
                "default", "exec-1", "route-a", "my-app",
                Instant.parse("2026-03-31T10:00:00Z"),
                List.of(proc));

        Integer count = jdbc.queryForObject(
                "SELECT count() FROM processor_executions WHERE execution_id = 'exec-1'",
                Integer.class);
        assertThat(count).isEqualTo(1);

        // Verify seq is stored
        Integer seq = jdbc.queryForObject(
                "SELECT seq FROM processor_executions WHERE execution_id = 'exec-1'",
                Integer.class);
        assertThat(seq).isEqualTo(1);
    }

    @Test
    void insertProcessorBatch_withIterations() {
        FlatProcessorRecord splitContainer = new FlatProcessorRecord(1, "split-1", "split");
        splitContainer.setIterationSize(3);
        splitContainer.setStatus(ExecutionStatus.COMPLETED);
        splitContainer.setStartTime(Instant.parse("2026-03-31T10:00:00Z"));
        splitContainer.setDurationMs(300L);

        FlatProcessorRecord child0 = new FlatProcessorRecord(2, "child-proc", "to");
        child0.setParentSeq(1);
        child0.setParentProcessorId("split-1");
        child0.setIteration(0);
        child0.setStatus(ExecutionStatus.COMPLETED);
        child0.setStartTime(Instant.parse("2026-03-31T10:00:00.100Z"));
        child0.setDurationMs(80L);
        child0.setResolvedEndpointUri("http://svc-a");
        child0.setInputBody("body0");
        child0.setOutputBody("out0");

        FlatProcessorRecord child1 = new FlatProcessorRecord(3, "child-proc", "to");
        child1.setParentSeq(1);
        child1.setParentProcessorId("split-1");
        child1.setIteration(1);
        child1.setStatus(ExecutionStatus.COMPLETED);
        child1.setStartTime(Instant.parse("2026-03-31T10:00:00.200Z"));
        child1.setDurationMs(90L);
        child1.setResolvedEndpointUri("http://svc-a");
        child1.setInputBody("body1");
        child1.setOutputBody("out1");

        FlatProcessorRecord child2 = new FlatProcessorRecord(4, "child-proc", "to");
        child2.setParentSeq(1);
        child2.setParentProcessorId("split-1");
        child2.setIteration(2);
        child2.setStatus(ExecutionStatus.COMPLETED);
        child2.setStartTime(Instant.parse("2026-03-31T10:00:00.300Z"));
        child2.setDurationMs(100L);
        child2.setResolvedEndpointUri("http://svc-a");
        child2.setInputBody("body2");
        child2.setOutputBody("out2");

        store.insertProcessorBatch(
                "default", "exec-2", "route-b", "my-app",
                Instant.parse("2026-03-31T10:00:00Z"),
                List.of(splitContainer, child0, child1, child2));

        Integer count = jdbc.queryForObject(
                "SELECT count() FROM processor_executions WHERE execution_id = 'exec-2'",
                Integer.class);
        assertThat(count).isEqualTo(4);

        // Verify iteration data on the split container
        Integer iterationSize = jdbc.queryForObject(
                "SELECT iteration_size FROM processor_executions " +
                "WHERE execution_id = 'exec-2' AND seq = 1",
                Integer.class);
        assertThat(iterationSize).isEqualTo(3);

        // Verify iteration index on a child
        Integer iteration = jdbc.queryForObject(
                "SELECT iteration FROM processor_executions " +
                "WHERE execution_id = 'exec-2' AND seq = 3",
                Integer.class);
        assertThat(iteration).isEqualTo(1);
    }

    @Test
    void insertExecutionBatch_emptyList_doesNothing() {
        store.insertExecutionBatch(List.of());

        Integer count = jdbc.queryForObject(
                "SELECT count() FROM executions", Integer.class);
        assertThat(count).isEqualTo(0);
    }

    @Test
    void insertExecutionBatch_replacingMergeTree_keepsLatestVersion() {
        MergedExecution v1 = new MergedExecution(
                "default", 1L, "exec-r", "route-a", "agent-1", "my-app", "default",
                "RUNNING", "corr-1", "exchange-1",
                Instant.parse("2026-03-31T10:00:00Z"),
                null, null,
                "", "", "", "", "", "",
                "", "FULL",
                "", "", "", "", "", "", "",
                "", "",
                false, false,
                null, null
        );

        MergedExecution v2 = new MergedExecution(
                "default", 2L, "exec-r", "route-a", "agent-1", "my-app", "default",
                "COMPLETED", "corr-1", "exchange-1",
                Instant.parse("2026-03-31T10:00:00Z"),
                Instant.parse("2026-03-31T10:00:05Z"),
                5000L,
                "", "", "", "", "", "",
                "", "FULL",
                "", "", "", "", "", "", "",
                "", "",
                false, false,
                null, null
        );

        store.insertExecutionBatch(List.of(v1));
        store.insertExecutionBatch(List.of(v2));

        // Force merge to apply ReplacingMergeTree deduplication
        jdbc.execute("OPTIMIZE TABLE executions FINAL");

        String status = jdbc.queryForObject(
                "SELECT status FROM executions " +
                "WHERE execution_id = 'exec-r'",
                String.class);
        assertThat(status).isEqualTo("COMPLETED");
    }
}
