package p.studio.compiler.specs;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;

import static org.junit.jupiter.api.Assertions.*;

class BackendConformanceMatrixSpecTest {
    private static final String MATRIX_RELATIVE_PATH =
            "docs/specs/compiler/22. Backend Spec-to-Test Conformance Matrix.md";
    private static final String MATRIX_HARD_GATE_ENV = "PROMETEU_MATRIX_HARD_GATE";
    private static final String CHANGED_FILES_ENV = "PROMETEU_CHANGED_FILES";
    private static final List<String> HARD_GATE_PATH_PREFIXES = List.of(
            "prometeu-compiler/prometeu-build-pipeline/",
            "prometeu-compiler/frontends/prometeu-frontend-pbs/",
            "prometeu-compiler/prometeu-frontend-api/",
            "prometeu-compiler/prometeu-compiler-core/");
    private static final Pattern LAST_UPDATED_PATTERN = Pattern.compile("Last Updated: \\d{4}-\\d{2}-\\d{2}");
    private static final List<String> REQUIRED_IDS = List.of(
            "G19-5.1.1",
            "G19-5.1.2",
            "G19-5.1.3",
            "G19-5.1.4",
            "G19-5.2.1",
            "G19-5.2.2",
            "G19-5.2.3",
            "G19-5.2.4",
            "G19-5.2.5",
            "G19-5.2.6",
            "G19-5.2.7",
            "G19-5.2.8",
            "G20-6.2",
            "G20-6.3",
            "G20-6.4",
            "G20-7.2",
            "G20-7.3",
            "G20-7.4",
            "G20-8.1",
            "G20-8.2",
            "G20-8.3",
            "G20-9.3",
            "G20-9.4.2",
            "G20-9.4.3",
            "G20-10",
            "G20-11.1",
            "G20-11.2",
            "G20-11.3",
            "G21-5",
            "G21-6.1",
            "G21-6.2",
            "G21-7.1",
            "G21-7.2",
            "G21-7.3",
            "G21-7.4",
            "G21-7.5",
            "G21-9.1",
            "G21-9.2",
            "G21-9.3",
            "PBS13-12.0",
            "PBS13-12.1.1",
            "PBS13-12.1.2",
            "PBS13-12.1.3",
            "PBS13-12.1.4",
            "PBS13-12.1.5",
            "PBS13-12.2.1",
            "PBS13-12.2.2",
            "PBS13-12.3.1",
            "PBS13-12.3.2",
            "PBS13-12.4.1",
            "PBS13-12.4.2",
            "PBS13-12.5");

    @Test
    void matrixMustExistAndContainRequiredRequirementRows() throws IOException {
        final var matrixPath = locateRepoRoot().resolve(MATRIX_RELATIVE_PATH);
        assertTrue(Files.isRegularFile(matrixPath), "matrix spec is missing: " + matrixPath);

        final var content = Files.readString(matrixPath);
        assertTrue(content.contains("Status Legend"), "matrix spec must contain a status legend section");
        assertTrue(LAST_UPDATED_PATTERN.matcher(content).find(), "matrix spec must declare a Last Updated date");

        for (final var requirementId : REQUIRED_IDS) {
            assertTrue(content.contains("| " + requirementId + " |"),
                    "matrix is missing requirement row: " + requirementId);
        }
    }

    @Test
    void matrixRequirementRowsMustUseKnownStatusValues() throws IOException {
        final var lines = Files.readAllLines(locateRepoRoot().resolve(MATRIX_RELATIVE_PATH));

        for (final var requirementId : REQUIRED_IDS) {
            final var row = findRequirementRow(lines, requirementId)
                    .orElseThrow(() -> new AssertionError("missing matrix row for requirement: " + requirementId));
            final var hasKnownStatus = row.contains("| pass |")
                    || row.contains("| partial |")
                    || row.contains("| missing |")
                    || row.contains("| deferred |");
            assertTrue(hasKnownStatus, "matrix row must declare known status for requirement: " + requirementId);
        }
    }

    @Test
    void hardGatePolicyMustFailWhenConformanceRelevantPathsChangeWithoutMatrixUpdate() {
        final var changedFiles = List.of(
                "prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/OptimizeIRVMService.java",
                "prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsFrontendCompiler.java");

        assertTrue(requiresMatrixUpdate(changedFiles));
    }

    @Test
    void hardGatePolicyMustPassWhenConformanceRelevantPathsChangeAndMatrixIsUpdated() {
        final var changedFiles = List.of(
                "prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/backend/irvm/OptimizeIRVMService.java",
                MATRIX_RELATIVE_PATH);

        assertFalse(requiresMatrixUpdate(changedFiles));
    }

    @Test
    void hardGatePolicyMustIgnoreNonConformanceChangesWithoutMatrixUpdate() {
        final var changedFiles = List.of(
                "README.md",
                "docs/pbs/agendas/18.0. Backend VM Pipeline - Orchestration Agenda.md");

        assertFalse(requiresMatrixUpdate(changedFiles));
    }

    @Test
    void matrixHardGateEnvironmentCheckMustRejectRelevantChangesWithoutMatrixUpdate() {
        if (!isTruthy(System.getenv(MATRIX_HARD_GATE_ENV))) {
            return;
        }
        final var changedFiles = parseChangedFiles(System.getenv(CHANGED_FILES_ENV));
        assertTrue(!changedFiles.isEmpty(),
                CHANGED_FILES_ENV + " must be provided when " + MATRIX_HARD_GATE_ENV + " is enabled");
        assertFalse(
                requiresMatrixUpdate(changedFiles),
                "conformance-relevant FE/BE changes require matrix update: " + MATRIX_RELATIVE_PATH);
    }

    private Optional<String> findRequirementRow(
            final List<String> lines,
            final String requirementId) {
        final var prefix = "| " + requirementId + " |";
        return lines.stream().filter(line -> line.startsWith(prefix)).findFirst();
    }

    private Path locateRepoRoot() {
        var cursor = Path.of(System.getProperty("user.dir")).toAbsolutePath().normalize();
        while (cursor != null) {
            final var hasDocs = Files.isDirectory(cursor.resolve("docs/compiler/general/specs"));
            final var hasCurrentDocs = Files.isDirectory(cursor.resolve("docs/specs/compiler"));
            final var hasCompiler = Files.isDirectory(cursor.resolve("prometeu-compiler"));
            if ((hasDocs || hasCurrentDocs) && hasCompiler) {
                return cursor;
            }
            cursor = cursor.getParent();
        }
        fail("could not locate repository root from working directory");
        throw new IllegalStateException("unreachable");
    }

    private boolean requiresMatrixUpdate(final List<String> changedFiles) {
        final var normalized = changedFiles.stream()
                .filter(path -> path != null && !path.isBlank())
                .map(path -> path.replace('\\', '/'))
                .toList();
        final var relevantTouched = normalized.stream().anyMatch(this::isConformanceRelevantPath);
        final var matrixTouched = normalized.stream().anyMatch(path -> path.equals(MATRIX_RELATIVE_PATH));
        return relevantTouched && !matrixTouched;
    }

    private boolean isConformanceRelevantPath(final String path) {
        for (final var prefix : HARD_GATE_PATH_PREFIXES) {
            if (path.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }

    private List<String> parseChangedFiles(final String raw) {
        if (raw == null || raw.isBlank()) {
            return List.of();
        }
        return Arrays.stream(raw.split("[,\\n]"))
                .map(String::trim)
                .filter(path -> !path.isBlank())
                .toList();
    }

    private boolean isTruthy(final String value) {
        if (value == null) {
            return false;
        }
        return switch (value.trim().toLowerCase()) {
            case "1", "true", "yes", "on" -> true;
            default -> false;
        };
    }
}
