package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
)

// reqRowRe captures a REQUIREMENTS.md table row's REQ-ID, Phase cell, and
// status in one pass. The leading .* is greedy so it consumes the
// Requirement and Priority cells (which may contain markdown-escaped pipes
// like `localhost\|linux\|proxmox` — see REQ-049) and backtracks to anchor
// the Phase + Status match at the END of the line, where those two columns
// always live. The status token is optionally wrapped in markdown bold
// (real rows use `**Complete**`; synthetic/future rows may use bare
// `Pending`), and may carry trailing notes (e.g. "**Complete** (P01
// shipped v0.2.1)") matched by [^|]* before the closing pipe.
var reqRowRe = regexp.MustCompile(`^\|\s*(REQ-\d+)\s*\|.*\|\s*([^|]*?)\s*\|\s*\*{0,2}(Complete|Pending)\*{0,2}[^|]*\|\s*$`)

// milestoneCompleteRe matches a ROADMAP.md milestone header that is marked
// COMPLETE. The bold span is substring-tolerant (GRILL #4): it matches
// `**COMPLETE**`, `**COMPLETE (merged to main via v0.3)**`, and any future
// variant where the word COMPLETE appears inside the bold span, possibly
// preceded or followed by non-asterisk text. The milestone version (v0.X)
// is captured.
var milestoneCompleteRe = regexp.MustCompile(`^##\s*Milestone\s+(v0\.\d+):.*—\s*\*\*[^*]*\bCOMPLETE\b[^*]*\*\*`)

// phaseRe extracts the milestone version from a REQUIREMENTS Phase cell such
// as `v0.7 P1`, `**v0.2 P01**`, `v0.2 P01–P04`, or bare `v0.7`. The cell may
// contain multiple milestone refs separated by `/` or `–`; each is extracted.
var phaseTokenRe = regexp.MustCompile(`v0\.\d+`)

// reqRow holds a parsed REQUIREMENTS.md row.
type reqRow struct {
	id     string
	phase  string // raw Phase cell (e.g. "v0.7 P1", "**v0.2 P01 / v0.3 P02**")
	status string // "Complete" or "Pending"
}

// milestoneVersions returns the distinct v0.X milestones referenced in the
// phase cell (e.g. "v0.7 P1" → ["v0.7"]; "v0.2 P01 / v0.3 P02" →
// ["v0.2","v0.3"]).
func (r reqRow) milestoneVersions() []string {
	matches := phaseTokenRe.FindAllString(r.phase, -1)
	seen := map[string]bool{}
	var out []string
	for _, m := range matches {
		if !seen[m] {
			seen[m] = true
			out = append(out, m)
		}
	}
	return out
}

func main() {
	roadmapPath := ".ciagent/ROADMAP.md"
	reqsPath := ".ciagent/REQUIREMENTS.md"
	if len(os.Args) > 1 {
		roadmapPath = os.Args[1]
	}
	if len(os.Args) > 2 {
		reqsPath = os.Args[2]
	}
	diff, count, err := verify(roadmapPath, reqsPath)
	if err != nil {
		fmt.Fprintf(os.Stderr, "verify-reqs: %v\n", err)
		os.Exit(2)
	}
	if len(diff) > 0 {
		fmt.Fprintf(os.Stderr, "requirements drift detected (%d):\n", len(diff))
		for _, line := range diff {
			fmt.Fprintln(os.Stderr, line)
		}
		os.Exit(1)
	}
	fmt.Printf("✓ %d requirements consistent with roadmap\n", count)
}

// verify parses the ROADMAP and REQUIREMENTS markdown and returns a diff
// listing of any drift. On success diff is nil and count is the number of
// consistent REQ rows. A non-nil error signals a parse/read failure (not
// drift); drift is reported via the diff slice.
func verify(roadmapPath, reqsPath string) (diff []string, count int, err error) {
	completeMilestones, err := parseRoadmap(roadmapPath)
	if err != nil {
		return nil, 0, fmt.Errorf("parse roadmap %q: %w", roadmapPath, err)
	}
	rows, err := parseRequirements(reqsPath)
	if err != nil {
		return nil, 0, fmt.Errorf("parse requirements %q: %w", reqsPath, err)
	}
	if len(rows) == 0 {
		return nil, 0, fmt.Errorf("no REQ rows found in %s", reqsPath)
	}

	type driftEntry struct {
		id      string
		current string
		want    string
		dir     string // "forward" or "reverse"
	}
	var drifts []driftEntry

	for _, r := range rows {
		milestones := r.milestoneVersions()
		anyComplete := false
		for _, m := range milestones {
			if completeMilestones[m] {
				anyComplete = true
				break
			}
		}
		// Forward assertion: a REQ whose milestone is COMPLETE in ROADMAP
		// must be marked Complete in REQUIREMENTS.
		if anyComplete && r.status != "Complete" {
			drifts = append(drifts, driftEntry{r.id, r.status, "Complete", "forward"})
		}
		// Reverse assertion (GRILL #4): a REQ marked Complete in
		// REQUIREMENTS must reference at least one milestone ROADMAP marks
		// COMPLETE. If all referenced milestones are NOT complete (or no
		// milestone is referenced), that is premature-Complete drift.
		if r.status == "Complete" && !anyComplete {
			drifts = append(drifts, driftEntry{r.id, r.status, "Pending (milestone not COMPLETE in ROADMAP)", "reverse"})
		}
	}

	sort.Slice(drifts, func(i, j int) bool { return drifts[i].id < drifts[j].id })
	for _, d := range drifts {
		diff = append(diff, fmt.Sprintf("  %s: status=%s, expected=%s (direction=%s)", d.id, d.current, d.want, d.dir))
	}

	consistent := len(rows) - len(drifts)
	return diff, consistent, nil
}

func parseRoadmap(path string) (map[string]bool, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	complete := map[string]bool{}
	sc := bufio.NewScanner(f)
	sc.Buffer(make([]byte, 1024*1024), 1024*1024)
	for sc.Scan() {
		line := sc.Text()
		m := milestoneCompleteRe.FindStringSubmatch(line)
		if m != nil {
			complete[m[1]] = true
		}
	}
	if err := sc.Err(); err != nil {
		return nil, err
	}
	return complete, nil
}

func parseRequirements(path string) ([]reqRow, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	var rows []reqRow
	sc := bufio.NewScanner(f)
	sc.Buffer(make([]byte, 1024*1024), 1024*1024)
	for sc.Scan() {
		line := sc.Text()
		m := reqRowRe.FindStringSubmatch(line)
		if m == nil {
			continue
		}
		rows = append(rows, reqRow{id: m[1], phase: strings.TrimSpace(m[2]), status: m[3]})
	}
	if err := sc.Err(); err != nil {
		return nil, err
	}
	return rows, nil
}
