package main

import (
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

// Tests run with cwd = package dir (cmd/verify-reqs), so `testdata/...`
// paths resolve relative to the package. The real-repo tests use a
// repoRoot helper to locate `.ciagent/...`.

func repoRoot(t *testing.T) string {
	t.Helper()
	wd, err := os.Getwd()
	if err != nil {
		t.Fatalf("getwd: %v", err)
	}
	return filepath.Join(wd, "..", "..")
}

// case (1): clean pair → no drift.
func TestVerify_clean(t *testing.T) {
	diff, count, err := verify(
		filepath.Join("testdata", "roadmap_clean.md"),
		filepath.Join("testdata", "requirements_clean.md"),
	)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(diff) != 0 {
		t.Fatalf("expected no drift, got:\n%s", strings.Join(diff, "\n"))
	}
	if count != 5 {
		t.Fatalf("expected 5 consistent rows, got %d", count)
	}
}

// case (2)+(3): drift pair → all drifts reported, both directions.
func TestVerify_drift(t *testing.T) {
	diff, _, err := verify(
		filepath.Join("testdata", "roadmap_drift.md"),
		filepath.Join("testdata", "requirements_drift.md"),
	)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(diff) != 4 {
		t.Fatalf("expected 4 drifts (REQ-002 forward, REQ-003 reverse, REQ-004 forward, REQ-005 forward), got %d:\n%s",
			len(diff), strings.Join(diff, "\n"))
	}
	joined := strings.Join(diff, "\n")
	for _, want := range []string{"REQ-002", "REQ-003", "REQ-004", "REQ-005"} {
		if !strings.Contains(joined, want) {
			t.Errorf("diff missing %s:\n%s", want, joined)
		}
	}
	if !strings.Contains(joined, "direction=reverse") {
		t.Errorf("expected a reverse-direction drift, got:\n%s", joined)
	}
	if !strings.Contains(joined, "direction=forward") {
		t.Errorf("expected a forward-direction drift, got:\n%s", joined)
	}
}

// case (4): missing args → defaults resolve to the repo's .ciagent/ files.
// Runs the program as a subprocess from the repo root.
func TestVerify_defaultArgs(t *testing.T) {
	if testing.Short() {
		t.Skip("subprocess test skipped in -short mode")
	}
	root := repoRoot(t)
	cmd := exec.Command("go", "run", "./cmd/verify-reqs")
	cmd.Dir = root
	out := &strings.Builder{}
	cmd.Stdout = out
	cmd.Stderr = out
	if err := cmd.Run(); err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			t.Fatalf("verify-reqs on real repo exited %d (should be 0): %s",
				exitErr.ExitCode(), out.String())
		}
		t.Fatalf("go run failed: %v", err)
	}
}

// case (5): malformed markdown (no REQ rows) → clear error, not silent pass.
func TestVerify_malformed(t *testing.T) {
	_, _, err := verify(
		filepath.Join("testdata", "roadmap_clean.md"),
		filepath.Join("testdata", "requirements_malformed.md"),
	)
	if err == nil {
		t.Fatal("expected error on malformed (no REQ rows) requirements, got nil")
	}
	if !strings.Contains(err.Error(), "no REQ rows") {
		t.Errorf("expected 'no REQ rows' error, got: %v", err)
	}
}

// case (6): missing file → clear error, not silent pass.
func TestVerify_missingFile(t *testing.T) {
	_, _, err := verify(
		filepath.Join("testdata", "roadmap_clean.md"),
		filepath.Join("testdata", "does_not_exist.md"),
	)
	if err == nil {
		t.Fatal("expected error on missing file, got nil")
	}
	if !strings.Contains(err.Error(), "does_not_exist.md") {
		t.Errorf("expected error to mention the missing file, got: %v", err)
	}
}

// GRILL #4 golden test: the v0.2-style
// `**COMPLETE (merged to main via v0.3)**` header MUST be recognized as a
// complete milestone by the substring-tolerant regex. The drift fixture's
// roadmap carries exactly this header on its v0.2 line, and the drift
// fixture's REQ-002 (v0.2 P1, Pending) would be silently skipped if the
// regex regressed to the exact `\*\*COMPLETE\*\*` form. TestVerify_drift
// already asserts REQ-002 is flagged forward-drift; this test makes the
// intent explicit and guards against a regex regression.
func TestVerify_v02SubstringTolerantHeader(t *testing.T) {
	diff, _, err := verify(
		filepath.Join("testdata", "roadmap_drift.md"),
		filepath.Join("testdata", "requirements_drift.md"),
	)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	// REQ-002 references milestone v0.2, whose header uses the
	// `**COMPLETE (merged to main via v0.3)**` variant. If the regex
	// regressed, v0.2 would not be marked complete and REQ-002 (Pending)
	// would NOT be flagged as forward drift.
	found := false
	for _, d := range diff {
		if strings.Contains(d, "REQ-002") && strings.Contains(d, "direction=forward") {
			found = true
			break
		}
	}
	if !found {
		t.Fatalf("REQ-002 forward drift not reported — substring-tolerant regex may have regressed; diff:\n%s",
			strings.Join(diff, "\n"))
	}
}

// Verify the actual repo passes (regression guard for the real docs).
func TestVerify_realRepo(t *testing.T) {
	if testing.Short() {
		t.Skip("real-repo test skipped in -short mode")
	}
	root := repoRoot(t)
	diff, count, err := verify(
		filepath.Join(root, ".ciagent", "ROADMAP.md"),
		filepath.Join(root, ".ciagent", "REQUIREMENTS.md"),
	)
	if err != nil {
		t.Fatalf("verify on real repo errored: %v", err)
	}
	if len(diff) != 0 {
		t.Fatalf("real repo has requirements drift (should be clean after SPECIFY):\n%s",
			strings.Join(diff, "\n"))
	}
	if count <= 0 {
		t.Fatalf("real repo reported %d consistent rows (expected > 0)", count)
	}
}
