package io.cameleer.server.app.license;

import io.cameleer.server.core.license.LicenseGate;
import io.cameleer.license.LicenseLimits;
import io.cameleer.server.core.runtime.Environment;
import io.cameleer.server.core.runtime.EnvironmentRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.event.EventListener;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * Recomputes ClickHouse per-environment TTL on every {@link LicenseChangedEvent}.
 *
 * <p>Spec §4.3 — when a license is installed, replaced, or expires, the effective
 * retention cap may change. For each (table, env) pair this listener emits one
 * {@code ALTER TABLE … MODIFY TTL <expr> WHERE environment = '<slug>'} statement
 * with {@code effective = min(licenseCap, env.configuredRetentionDays)}.</p>
 *
 * <p>ClickHouse 22.3+ supports per-row TTL via the {@code WHERE} predicate; the
 * project's CH version (24.12) is well above that floor. ClickHouse failures are
 * logged and swallowed — TTL recompute is best-effort and must not propagate
 * to the originating license install/revalidate path.</p>
 *
 * <p>NOTE: {@code route_diagrams} has no TTL clause in {@code init.sql} — it's a
 * {@code ReplacingMergeTree} keyed on content_hash, not a time-series table —
 * so it is intentionally excluded here. {@code server_metrics} has no
 * {@code environment} column (server-wide) so it is also excluded; its 90-day
 * cap is fixed in the schema.</p>
 */
@Component
public class RetentionPolicyApplier {

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

    /** (table, time column, license cap key, env-configured-days extractor). */
    private record TableSpec(String table, String timeCol, String capKey, Extractor extractor) {}

    @FunctionalInterface
    private interface Extractor {
        int days(Environment env);
    }

    /**
     * Tables with a TTL clause AND an {@code environment} column in {@code init.sql}.
     * Verified against the schema at task time — keep in sync if new retention-bound
     * tables are added.
     */
    static final List<TableSpec> SPECS = List.of(
            new TableSpec("executions",           "start_time",   "max_execution_retention_days", Environment::executionRetentionDays),
            new TableSpec("processor_executions", "start_time",   "max_execution_retention_days", Environment::executionRetentionDays),
            new TableSpec("logs",                 "timestamp",    "max_log_retention_days",       Environment::logRetentionDays),
            new TableSpec("agent_metrics",        "collected_at", "max_metric_retention_days",    Environment::metricRetentionDays),
            new TableSpec("agent_events",         "timestamp",    "max_metric_retention_days",    Environment::metricRetentionDays)
    );

    private final LicenseGate gate;
    private final EnvironmentRepository envRepo;
    private final JdbcTemplate clickhouseJdbc;

    public RetentionPolicyApplier(LicenseGate gate,
                                  EnvironmentRepository envRepo,
                                  @Qualifier("clickHouseJdbcTemplate") JdbcTemplate clickhouseJdbc) {
        this.gate = gate;
        this.envRepo = envRepo;
        this.clickhouseJdbc = clickhouseJdbc;
    }

    @EventListener(LicenseChangedEvent.class)
    @Async
    public void onLicenseChanged(LicenseChangedEvent event) {
        LicenseLimits limits;
        try {
            limits = gate.getEffectiveLimits();
        } catch (Exception e) {
            log.warn("Skipping TTL recompute — could not read effective limits: {}", e.getMessage());
            return;
        }

        List<Environment> envs;
        try {
            envs = envRepo.findAll();
        } catch (Exception e) {
            log.warn("Skipping TTL recompute — could not load environments: {}", e.getMessage());
            return;
        }

        log.info("License changed (state={}) — recomputing TTL across {} environment(s) and {} table(s)",
                event.state(), envs.size(), SPECS.size());

        for (Environment env : envs) {
            for (TableSpec spec : SPECS) {
                int cap = limits.get(spec.capKey);
                int configured = spec.extractor.days(env);
                int effective = Math.min(cap, configured);
                // Slugs are regex-validated `^[a-z0-9][a-z0-9-]{0,63}$`, so the replacement
                // is defense-in-depth — single quotes can never be present.
                String envLiteral = env.slug().replace("'", "''");
                String sql = "ALTER TABLE " + spec.table
                        + " MODIFY TTL toDateTime(" + spec.timeCol
                        + ") + INTERVAL " + effective + " DAY DELETE"
                        + " WHERE environment = '" + envLiteral + "'";
                try {
                    clickhouseJdbc.execute(sql);
                    log.info("Applied TTL: table={} env={} days={} (cap={}, configured={})",
                            spec.table, env.slug(), effective, cap, configured);
                } catch (Exception e) {
                    log.warn("Failed to apply TTL for table={} env={}: {}",
                            spec.table, env.slug(), e.getMessage());
                }
            }
        }
    }
}
