/*
 * Copyright (c) nosqlbench
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.nosqlbench.nb.mql.testdata;

import io.nosqlbench.nb.api.components.core.NBComponent;
import io.nosqlbench.nb.api.components.core.NBBaseComponent;
import io.nosqlbench.nb.api.engine.metrics.instruments.MetricCategory;
import io.nosqlbench.nb.api.engine.metrics.instruments.NBMetricCounter;
import io.nosqlbench.nb.api.engine.metrics.instruments.NBMetricGauge;
import io.nosqlbench.nb.api.engine.metrics.instruments.NBMetricTimer;
import io.nosqlbench.nb.api.engine.metrics.reporters.SqliteSnapshotReporter;
import io.nosqlbench.nb.api.engine.metrics.reporters.MetricInstanceFilter;
import io.nosqlbench.nb.api.engine.metrics.DeltaHdrHistogramReservoir;
import io.nosqlbench.nb.api.engine.metrics.view.MetricsView;
import io.nosqlbench.nb.api.labels.NBLabels;

import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Generates test databases with various metric scenarios for testing query commands.
 * Each generator method creates a database with specific characteristics documented
 * in TEST_DATABASES.md.
 */
public class TestDatabaseGenerator {

    /**
     * Generate simple_counter.db
     *
     * A basic counter metric with two label dimensions over 5 minutes.
     *
     * Schema:
     * - Metric: activity_ops_total (COUNTER)
     * - Labels: activity={read,write}, host=server1
     * - Time range: 5 minutes (10 snapshots @ 30s intervals)
     * - Values: Linear growth (read: 0→100, write: 0→150)
     *
     * Purpose: Test instant queries, simple ranges, basic rate calculations
     */
    public static void generateSimpleCounter(Path outputPath, NBComponent parent) throws Exception {
        String jdbcUrl = "jdbc:sqlite:" + outputPath.toAbsolutePath();

        NBLabels sessionLabels = NBLabels.forKV("session", "simple_counter_test");

        try (SqliteSnapshotReporter reporter = new SqliteSnapshotReporter(
                parent, jdbcUrl, 30000L, new MetricInstanceFilter(), sessionLabels)) {

            // Add session metadata
            Map<String, String> metadata = Map.of(
                "nb.version", "5.25.0-TEST",
                "nb.commandline", "nb5 mql test simple_counter",
                "nb.hardware", "Test harness - 4-core 8GB"
            );
            reporter.onSessionMetadata(sessionLabels, metadata);

            // Base timestamp: 2025-10-23 10:00:00 UTC
            long baseTime = 1729681200000L;
            long intervalMs = 30000L;

            for (int i = 0; i < 10; i++) {
                List<NBMetricCounter> counters = new ArrayList<>();

                // Create read counter
                NBMetricCounter readCounter = new NBMetricCounter(
                    NBLabels.forKV("name", "activity_ops", "activity", "read", "host", "server1"),
                    "ops",
                    "operations",
                    MetricCategory.Core
                );
                readCounter.inc(i * 10); // 0, 10, 20, ..., 90

                // Create write counter
                NBMetricCounter writeCounter = new NBMetricCounter(
                    NBLabels.forKV("name", "activity_ops", "activity", "write", "host", "server1"),
                    "ops",
                    "operations",
                    MetricCategory.Core
                );
                writeCounter.inc(i * 15); // 0, 15, 30, ..., 135

                counters.add(readCounter);
                counters.add(writeCounter);

                // Create MetricsView and report snapshot
                // Note: Timestamps will be auto-generated by the reporter
                MetricsView view = MetricsView.capture(counters, intervalMs);
                reporter.onMetricsSnapshot(view);

                // Sleep to create distinct timestamps
                Thread.sleep(100);
            }
        }
    }

    /**
     * Generate multi_dimensional.db
     *
     * Complex metrics with 4 dimensional labels for testing grouping and filtering.
     *
     * Schema:
     * - Metric: request_total (COUNTER)
     * - Labels: activity={read,write}, host={server1,server2,server3},
     *           region={us-east,us-west}, env={prod,staging}
     * - Combinations: 2 * 3 * 2 * 2 = 24 unique label sets
     * - Time range: 2 minutes (4 snapshots @ 30s)
     *
     * Purpose: Test label filtering, aggregations with grouping, topk queries
     */
    public static void generateMultiDimensional(Path outputPath, NBComponent parent) throws Exception {
        String jdbcUrl = "jdbc:sqlite:" + outputPath.toAbsolutePath();

        NBLabels sessionLabels = NBLabels.forKV("session", "multi_dimensional_test");

        try (SqliteSnapshotReporter reporter = new SqliteSnapshotReporter(
                parent, jdbcUrl, 30000L, new MetricInstanceFilter(), sessionLabels)) {

            // Add session metadata
            Map<String, String> metadata = Map.of(
                "nb.version", "5.25.0-TEST",
                "nb.commandline", "nb5 mql test multi_dimensional",
                "nb.hardware", "Test harness - 4-core 8GB"
            );
            reporter.onSessionMetadata(sessionLabels, metadata);

            long baseTime = 1729681200000L;
            long intervalMs = 30000L;
            String[] activities = {"read", "write"};
            String[] hosts = {"server1", "server2", "server3"};
            String[] regions = {"us-east", "us-west"};
            String[] envs = {"prod", "staging"};

            for (int snapshot = 0; snapshot < 4; snapshot++) {
                List<NBMetricCounter> counters = new ArrayList<>();
                int counter = 0;

                for (String activity : activities) {
                    for (String host : hosts) {
                        for (String region : regions) {
                            for (String env : envs) {
                                NBMetricCounter metric = new NBMetricCounter(
                                    NBLabels.forKV(
                                        "name", "requests",
                                        "activity", activity,
                                        "host", host,
                                        "region", region,
                                        "env", env
                                    ),
                                    "requests",
                                    "operations",
                                    MetricCategory.Core
                                );

                                // Value varies by combination and snapshot
                                long value = (counter * 10) + (snapshot * 100);
                                metric.inc(value);
                                counters.add(metric);
                                counter++;
                            }
                        }
                    }
                }

                MetricsView view = MetricsView.capture(counters, intervalMs);
                reporter.onMetricsSnapshot(view);
                Thread.sleep(100);
            }
        }
    }

    /**
     * Generate latency_timers.db
     *
     * Summary/timer metrics with quantiles for latency analysis.
     *
     * Schema:
     * - Metric: operation_latency_ms (TIMER/SUMMARY)
     * - Labels: operation={select,insert,update,delete}
     * - Quantiles: p50, p75, p90, p95, p98, p99, p999
     * - Statistics: count, sum, min, max, mean, stddev
     * - Time range: 3 minutes (6 snapshots @ 30s)
     *
     * Purpose: Test quantile queries, statistical aggregations
     */
    public static void generateLatencyTimers(Path outputPath, NBComponent parent) throws Exception {
        String jdbcUrl = "jdbc:sqlite:" + outputPath.toAbsolutePath();

        NBLabels sessionLabels = NBLabels.forKV("session", "latency_timers_test");

        try (SqliteSnapshotReporter reporter = new SqliteSnapshotReporter(
                parent, jdbcUrl, 30000L, new MetricInstanceFilter(), sessionLabels)) {

            // Add session metadata
            Map<String, String> metadata = Map.of(
                "nb.version", "5.25.0-TEST",
                "nb.commandline", "nb5 mql test latency_timers",
                "nb.hardware", "Test harness - 4-core 8GB"
            );
            reporter.onSessionMetadata(sessionLabels, metadata);

            long baseTime = 1729681200000L;
            long intervalMs = 30000L;
            String[] operations = {"select", "insert", "update", "delete"};

            for (int snapshot = 0; snapshot < 6; snapshot++) {
                List<NBMetricTimer> timers = new ArrayList<>();

                for (int opIdx = 0; opIdx < operations.length; opIdx++) {
                    String operation = operations[opIdx];
                    NBLabels labels = NBLabels.forKV(
                        "name", "operation_latency",
                        "operation", operation
                    );

                    NBMetricTimer timer = new NBMetricTimer(
                        labels,
                        new DeltaHdrHistogramReservoir(labels, 3),
                        "latency",
                        "nanoseconds",
                        MetricCategory.Core
                    );

                    // Vary latencies by operation type and over time
                    long baseLatency = (opIdx + 1) * 10_000_000L; // 10ms, 20ms, 30ms, 40ms in nanoseconds
                    long drift = snapshot * 2_000_000L; // 2ms increase per snapshot

                    // Record latencies with some variation
                    int recordCount = 100 + (snapshot * 20);
                    for (int i = 0; i < recordCount; i++) {
                        // Create a distribution of latencies
                        long latency;
                        if (i < recordCount * 0.5) {
                            latency = baseLatency + drift; // p50
                        } else if (i < recordCount * 0.75) {
                            latency = (long) ((baseLatency + drift) * 1.5); // p75
                        } else if (i < recordCount * 0.95) {
                            latency = (long) ((baseLatency + drift) * 2.0); // p95
                        } else {
                            latency = (long) ((baseLatency + drift) * 3.0); // p99
                        }
                        timer.update(Duration.ofNanos(latency));
                    }

                    timers.add(timer);
                }

                long timestamp = baseTime + (snapshot * intervalMs);
                MetricsView view = MetricsView.capture(timers, intervalMs);
                reporter.onMetricsSnapshot(view);
                Thread.sleep(100);
            }
        }
    }

    /**
     * Generate rate_calculations.db
     *
     * Counter metrics with predictable growth patterns for testing rate() and increase().
     *
     * Schema:
     * - Metrics: counter_total (COUNTER)
     * - Labels: pattern={linear,exponential,step}
     * - Time range: 1 hour (120 snapshots @ 30s)
     * - Patterns:
     *   - linear: grows by 10 per snapshot (constant rate)
     *   - exponential: grows by 1.01x per snapshot
     *   - step: 100 for 10 snapshots, then jump to 200 for 10, etc.
     *
     * Purpose: Test rate() and increase() with known expected results
     */
    public static void generateRateCalculations(Path outputPath, NBComponent parent) throws Exception {
        String jdbcUrl = "jdbc:sqlite:" + outputPath.toAbsolutePath();

        NBLabels sessionLabels = NBLabels.forKV("session", "rate_calculations_test");

        try (SqliteSnapshotReporter reporter = new SqliteSnapshotReporter(
                parent, jdbcUrl, 30000L, new MetricInstanceFilter(), sessionLabels)) {

            // Add session metadata
            Map<String, String> metadata = Map.of(
                "nb.version", "5.25.0-TEST",
                "nb.commandline", "nb5 mql test rate_calculations",
                "nb.hardware", "Test harness - 4-core 8GB"
            );
            reporter.onSessionMetadata(sessionLabels, metadata);

            long baseTime = 1729681200000L;
            long intervalMs = 30000L;
            double exponentialValue = 100.0;

            for (int i = 0; i < 120; i++) {
                List<NBMetricCounter> counters = new ArrayList<>();

                // Linear counter: constant rate of 10 per 30s = 0.333 ops/sec
                NBMetricCounter linearCounter = new NBMetricCounter(
                    NBLabels.forKV("name", "patterns", "pattern", "linear"),
                    "counter",
                    "operations",
                    MetricCategory.Core
                );
                linearCounter.inc(i * 10);
                counters.add(linearCounter);

                // Exponential counter: exponential growth
                exponentialValue *= 1.01; // 1% growth per snapshot
                NBMetricCounter expCounter = new NBMetricCounter(
                    NBLabels.forKV("name", "patterns", "pattern", "exponential"),
                    "counter",
                    "operations",
                    MetricCategory.Core
                );
                expCounter.inc((long) exponentialValue);
                counters.add(expCounter);

                // Step counter: steps every 10 snapshots
                long stepValue = ((i / 10) + 1) * 100;
                NBMetricCounter stepCounter = new NBMetricCounter(
                    NBLabels.forKV("name", "patterns", "pattern", "step"),
                    "counter",
                    "operations",
                    MetricCategory.Core
                );
                stepCounter.inc(stepValue);
                counters.add(stepCounter);

                MetricsView view = MetricsView.capture(counters, intervalMs);
                reporter.onMetricsSnapshot(view);
                Thread.sleep(50); // Shorter sleep for many snapshots
            }
        }
    }

    /**
     * Generate examples.db
     *
     * API request metrics for testing TopK, Range, Increase, Rate queries.
     *
     * Schema:
     * - Metrics: api_requests_total (COUNTER)
     * - Labels: method={GET,POST,PUT}, status={200,404,500}, endpoint={/api/users,/api/orders,/api/products}
     * - Time range: 5 snapshots over 2 minutes
     * - Values: Vary from 1000 to 11000 across label combinations
     *
     * Purpose: Test complex queries with multiple dimensions and TopK
     */
    public static void generateExamples(Path outputPath, NBComponent parent) throws Exception {
        String jdbcUrl = "jdbc:sqlite:" + outputPath.toAbsolutePath();

        NBLabels sessionLabels = NBLabels.forKV("session", "examples_test");

        try (SqliteSnapshotReporter reporter = new SqliteSnapshotReporter(
                parent, jdbcUrl, 30000L, new MetricInstanceFilter(), sessionLabels)) {

            // Add session metadata
            Map<String, String> metadata = Map.of(
                "nb.version", "5.25.0-TEST",
                "nb.commandline", "nb5 mql test examples",
                "nb.hardware", "Test harness - 4-core 8GB"
            );
            reporter.onSessionMetadata(sessionLabels, metadata);

            long baseTime = 1729681200000L;
            long intervalMs = 30000L;

            // Generate 5 snapshots
            for (int i = 0; i < 5; i++) {
                List<NBMetricCounter> counters = new ArrayList<>();

                // Create counters for different method/status/endpoint combinations
                String[] methods = {"GET", "POST", "PUT"};
                String[] statuses = {"200", "404", "500"};
                String[] endpoints = {"/api/users", "/api/orders", "/api/products"};

                int value = 600;
                for (String method : methods) {
                    for (String status : statuses) {
                        for (String endpoint : endpoints) {
                            NBMetricCounter counter = new NBMetricCounter(
                                NBLabels.forKV(
                                    "name", "api_requests_total",
                                    "method", method,
                                    "status", status,
                                    "endpoint", endpoint
                                ),
                                "counter",
                                "operations",
                                MetricCategory.Core
                            );
                            counter.inc(value + (i * 100)); // Increment over time
                            counters.add(counter);
                            value += 385; // Vary values across combinations (top will be ~11000)
                        }
                    }
                }

                MetricsView view = MetricsView.capture(counters, intervalMs);
                reporter.onMetricsSnapshot(view);
                Thread.sleep(100);
            }
        }
    }

    /**
     * Main method to generate all test databases.
     * Run this to regenerate test data after schema changes.
     *
     * <p><strong>IMPORTANT</strong>: Must be run from the nosqlbench project root directory:</p>
     * <pre>
     * mvn exec:java -pl nb-apis/nb-mql-api \
     *   -Dexec.mainClass="io.nosqlbench.nb.mql.testdata.TestDatabaseGenerator" \
     *   -Dexec.classpathScope=test
     * </pre>
     */
    public static void main(String[] args) throws Exception {
        Path testDataDir = Path.of("src/test/resources/testdata").toAbsolutePath().normalize();

        // Create a minimal NBComponent for the reporter
        NBComponent root = new NBBaseComponent(null, NBLabels.forKV("generator", "testdata"));

        System.out.println("Generating test databases...");
        System.out.println("Output directory: " + testDataDir);

        generateSimpleCounter(testDataDir.resolve("simple_counter.db"), root);
        System.out.println("✓ Generated simple_counter.db");

        generateMultiDimensional(testDataDir.resolve("multi_dimensional.db"), root);
        System.out.println("✓ Generated multi_dimensional.db");

        generateLatencyTimers(testDataDir.resolve("latency_timers.db"), root);
        System.out.println("✓ Generated latency_timers.db");

        generateRateCalculations(testDataDir.resolve("rate_calculations.db"), root);
        System.out.println("✓ Generated rate_calculations.db");

        generateExamples(testDataDir.resolve("examples.db"), root);
        System.out.println("✓ Generated examples.db");

        System.out.println("\nAll test databases generated successfully!");
        System.out.println("Location: " + testDataDir.toAbsolutePath());
    }
}
