package org.vaadin.svgvis;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Stores and compares performance baseline readings.
 * Results are saved to a local file for tracking performance over time.
 */
public class PerformanceBaseline {

    private static final String BASELINE_FILE = "performance-baseline.properties";
    private static final double DEFAULT_TOLERANCE = 0.5; // 50% tolerance by default

    private final Properties properties;
    private final Path baselinePath;
    private final Map<String, Long> currentReadings = new LinkedHashMap<>();

    public PerformanceBaseline() {
        this.baselinePath = Path.of(BASELINE_FILE);
        this.properties = new Properties();
        loadBaseline();
    }

    /**
     * Records a performance reading for the given test key.
     */
    public void record(String key, long nanoseconds) {
        currentReadings.put(key, nanoseconds);
    }

    /**
     * Checks if the current reading is within acceptable tolerance of baseline.
     * Returns true if this is a new test (no baseline exists).
     */
    public boolean isWithinTolerance(String key, double toleranceMultiplier) {
        String baselineKey = "baseline." + key;
        String baselineValue = properties.getProperty(baselineKey);

        if (baselineValue == null) {
            return true; // No baseline yet, accept any value
        }

        Long currentValue = currentReadings.get(key);
        if (currentValue == null) {
            return true; // Not recorded yet
        }

        long baseline = Long.parseLong(baselineValue);
        long threshold = (long) (baseline * (1 + toleranceMultiplier));

        return currentValue <= threshold;
    }

    /**
     * Gets the baseline value for a key, or -1 if not found.
     */
    public long getBaseline(String key) {
        String baselineValue = properties.getProperty("baseline." + key);
        return baselineValue != null ? Long.parseLong(baselineValue) : -1;
    }

    /**
     * Gets the current reading for a key, or -1 if not recorded.
     */
    public long getCurrentReading(String key) {
        return currentReadings.getOrDefault(key, -1L);
    }

    /**
     * Asserts that performance is within tolerance, with detailed message on failure.
     */
    public void assertWithinTolerance(String key, double toleranceMultiplier) {
        long baseline = getBaseline(key);
        long current = getCurrentReading(key);

        if (baseline == -1) {
            System.out.printf("  [NEW] %s: %s (no baseline yet)%n", key, formatNanos(current));
            return;
        }

        long threshold = (long) (baseline * (1 + toleranceMultiplier));
        double changePercent = ((double) current / baseline - 1) * 100;

        if (current > threshold) {
            throw new AssertionError(String.format(
                    "Performance regression detected for '%s': %s (was %s, %.1f%% slower, threshold: %.0f%%)",
                    key, formatNanos(current), formatNanos(baseline), changePercent, toleranceMultiplier * 100));
        }

        String status = changePercent > 0 ? "slower" : "faster";
        System.out.printf("  [OK] %s: %s (baseline: %s, %.1f%% %s)%n",
                key, formatNanos(current), formatNanos(baseline), Math.abs(changePercent), status);
    }

    /**
     * Saves all current readings as new baseline.
     */
    public void saveBaseline() {
        // Update baseline values
        for (Map.Entry<String, Long> entry : currentReadings.entrySet()) {
            properties.setProperty("baseline." + entry.getKey(), String.valueOf(entry.getValue()));
        }

        // Update metadata
        properties.setProperty("meta.lastUpdated", Instant.now().toString());
        properties.setProperty("meta.cpuModel", getCpuModel());
        properties.setProperty("meta.cpuCores", String.valueOf(getAvailableProcessors()));
        properties.setProperty("meta.osName", System.getProperty("os.name"));
        properties.setProperty("meta.osVersion", System.getProperty("os.version"));
        properties.setProperty("meta.javaVersion", System.getProperty("java.version"));
        properties.setProperty("meta.javaVendor", System.getProperty("java.vendor"));

        try (Writer writer = Files.newBufferedWriter(baselinePath)) {
            properties.store(writer, "Performance Baseline - Auto-generated by RenderingPerformanceTest");
            System.out.println("\nBaseline saved to: " + baselinePath.toAbsolutePath());
        } catch (IOException e) {
            System.err.println("Warning: Could not save baseline: " + e.getMessage());
        }
    }

    /**
     * Prints CPU and system information.
     */
    public void printSystemInfo() {
        System.out.println("=== System Information ===");
        System.out.println("CPU Model:    " + getCpuModel());
        System.out.println("CPU Cores:    " + getAvailableProcessors());
        System.out.println("OS:           " + System.getProperty("os.name") + " " + System.getProperty("os.version"));
        System.out.println("Java:         " + System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")");
        System.out.println("Architecture: " + System.getProperty("os.arch"));

        // Print previous baseline info if exists
        String lastUpdated = properties.getProperty("meta.lastUpdated");
        if (lastUpdated != null) {
            System.out.println("\n=== Baseline Info ===");
            System.out.println("Last Updated: " + lastUpdated);
            System.out.println("Baseline CPU: " + properties.getProperty("meta.cpuModel", "unknown"));
            System.out.println("Baseline OS:  " + properties.getProperty("meta.osName", "unknown"));
        } else {
            System.out.println("\nNo existing baseline found - will create new baseline.");
        }
        System.out.println();
    }

    /**
     * Prints a summary of all recorded readings vs baseline.
     */
    public void printSummary() {
        System.out.println("\n=== Performance Summary ===");
        for (String key : currentReadings.keySet()) {
            long baseline = getBaseline(key);
            long current = getCurrentReading(key);

            if (baseline == -1) {
                System.out.printf("  %s: %s (NEW)%n", key, formatNanos(current));
            } else {
                double changePercent = ((double) current / baseline - 1) * 100;
                String indicator = changePercent > 20 ? "⚠️" : changePercent < -10 ? "✓" : " ";
                System.out.printf("  %s %s: %s (baseline: %s, %+.1f%%)%n",
                        indicator, key, formatNanos(current), formatNanos(baseline), changePercent);
            }
        }
    }

    private void loadBaseline() {
        if (Files.exists(baselinePath)) {
            try (Reader reader = Files.newBufferedReader(baselinePath)) {
                properties.load(reader);
            } catch (IOException e) {
                System.err.println("Warning: Could not load baseline: " + e.getMessage());
            }
        }
    }

    private String getCpuModel() {
        // Try to get CPU model from system
        String osName = System.getProperty("os.name").toLowerCase();

        if (osName.contains("mac")) {
            return execCommand("sysctl", "-n", "machdep.cpu.brand_string");
        } else if (osName.contains("linux")) {
            String model = execCommand("sh", "-c", "cat /proc/cpuinfo | grep 'model name' | head -1 | cut -d: -f2");
            return model.trim();
        } else if (osName.contains("windows")) {
            return System.getenv("PROCESSOR_IDENTIFIER");
        }

        return "Unknown CPU";
    }

    private int getAvailableProcessors() {
        return Runtime.getRuntime().availableProcessors();
    }

    private String execCommand(String... command) {
        try {
            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectErrorStream(true);
            Process process = pb.start();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line = reader.readLine();
                process.waitFor();
                return line != null ? line.trim() : "Unknown";
            }
        } catch (Exception e) {
            return "Unknown";
        }
    }

    private String formatNanos(long nanos) {
        if (nanos < 0) return "N/A";
        if (nanos < 1000) {
            return nanos + " ns";
        } else if (nanos < 1_000_000) {
            return String.format("%.1f µs", nanos / 1000.0);
        } else if (nanos < 1_000_000_000) {
            return String.format("%.2f ms", nanos / 1_000_000.0);
        } else {
            return String.format("%.2f s", nanos / 1_000_000_000.0);
        }
    }
}
