package com.cameleer.server.app.ingestion;

import com.cameleer.server.app.config.IngestionConfig;
import com.cameleer.server.app.search.ClickHouseLogStore;
import com.cameleer.server.app.storage.ClickHouseExecutionStore;
import com.cameleer.server.core.ingestion.BufferedLogEntry;
import com.cameleer.server.core.ingestion.ChunkAccumulator;
import com.cameleer.server.core.ingestion.MergedExecution;
import com.cameleer.server.core.ingestion.WriteBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.SmartLifecycle;
import org.springframework.scheduling.annotation.Scheduled;

import com.cameleer.server.app.metrics.ServerMetrics;

import java.time.Duration;
import java.util.List;

/**
 * Scheduled flush task for ClickHouse execution and processor write buffers.
 * <p>
 * Drains both buffers on a fixed interval and delegates batch inserts to
 * {@link ClickHouseExecutionStore}. Also periodically sweeps stale exchanges
 * from the {@link ChunkAccumulator}.
 * <p>
 * Not a {@code @Component} — instantiated as a {@code @Bean} in StorageBeanConfig.
 */
public class ExecutionFlushScheduler implements SmartLifecycle {

    private static final Logger log = LoggerFactory.getLogger(ExecutionFlushScheduler.class);

    private final WriteBuffer<MergedExecution> executionBuffer;
    private final WriteBuffer<ChunkAccumulator.ProcessorBatch> processorBuffer;
    private final WriteBuffer<BufferedLogEntry> logBuffer;
    private final ClickHouseExecutionStore executionStore;
    private final ClickHouseLogStore logStore;
    private final ChunkAccumulator accumulator;
    private final ServerMetrics serverMetrics;
    private final int batchSize;
    private volatile boolean running = false;

    public ExecutionFlushScheduler(WriteBuffer<MergedExecution> executionBuffer,
                                   WriteBuffer<ChunkAccumulator.ProcessorBatch> processorBuffer,
                                   WriteBuffer<BufferedLogEntry> logBuffer,
                                   ClickHouseExecutionStore executionStore,
                                   ClickHouseLogStore logStore,
                                   ChunkAccumulator accumulator,
                                   IngestionConfig config,
                                   ServerMetrics serverMetrics) {
        this.executionBuffer = executionBuffer;
        this.processorBuffer = processorBuffer;
        this.logBuffer = logBuffer;
        this.executionStore = executionStore;
        this.logStore = logStore;
        this.accumulator = accumulator;
        this.batchSize = config.getBatchSize();
        this.serverMetrics = serverMetrics;
    }

    @Scheduled(fixedDelayString = "${cameleer.server.ingestion.flushintervalms:1000}")
    public void flush() {
        try {
            List<MergedExecution> executions = executionBuffer.drain(batchSize);
            if (!executions.isEmpty()) {
                long start = System.nanoTime();
                executionStore.insertExecutionBatch(executions);
                serverMetrics.recordFlushDuration("execution",
                        Duration.ofNanos(System.nanoTime() - start));
                log.debug("Flushed {} executions to ClickHouse", executions.size());
            }
        } catch (Exception e) {
            log.error("Failed to flush executions", e);
        }

        try {
            List<ChunkAccumulator.ProcessorBatch> batches = processorBuffer.drain(batchSize);
            if (!batches.isEmpty()) {
                long start = System.nanoTime();
                executionStore.insertProcessorBatches(batches);
                serverMetrics.recordFlushDuration("processor",
                        Duration.ofNanos(System.nanoTime() - start));
                log.debug("Flushed {} processor batches to ClickHouse", batches.size());
            }
        } catch (Exception e) {
            log.error("Failed to flush processor batches", e);
        }

        try {
            List<BufferedLogEntry> logEntries = logBuffer.drain(batchSize);
            if (!logEntries.isEmpty()) {
                long start = System.nanoTime();
                logStore.insertBufferedBatch(logEntries);
                serverMetrics.recordFlushDuration("log",
                        Duration.ofNanos(System.nanoTime() - start));
                log.debug("Flushed {} log entries to ClickHouse", logEntries.size());
            }
        } catch (Exception e) {
            log.error("Failed to flush log entries", e);
        }
    }

    @Scheduled(fixedDelay = 60_000)
    public void sweepStale() {
        try {
            accumulator.sweepStale();
        } catch (Exception e) {
            log.error("Failed to sweep stale exchanges", e);
        }
    }

    @Override
    public void start() {
        running = true;
    }

    @Override
    public void stop() {
        // Drain remaining executions on shutdown
        while (executionBuffer.size() > 0) {
            List<MergedExecution> batch = executionBuffer.drain(batchSize);
            if (batch.isEmpty()) break;
            try {
                executionStore.insertExecutionBatch(batch);
            } catch (Exception e) {
                log.error("Failed to flush executions during shutdown", e);
                break;
            }
        }
        // Drain remaining processor batches on shutdown
        while (processorBuffer.size() > 0) {
            List<ChunkAccumulator.ProcessorBatch> batches = processorBuffer.drain(batchSize);
            if (batches.isEmpty()) break;
            try {
                executionStore.insertProcessorBatches(batches);
            } catch (Exception e) {
                log.error("Failed to flush processor batches during shutdown", e);
                break;
            }
        }
        // Drain remaining log entries on shutdown
        while (logBuffer.size() > 0) {
            List<BufferedLogEntry> entries = logBuffer.drain(batchSize);
            if (entries.isEmpty()) break;
            try {
                logStore.insertBufferedBatch(entries);
            } catch (Exception e) {
                log.error("Failed to flush log entries during shutdown", e);
                break;
            }
        }
        running = false;
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    @Override
    public int getPhase() {
        return Integer.MAX_VALUE - 1;
    }
}
