package webui

import (
	"regexp"
	"strconv"
	"strings"
	"testing"
)

// pdfHorizontalRules extracts every horizontal line the PDF draws, as (y, width).
// The content stream draws a line as: "<w> w  <x1> <y> m  <x2> <y> l  S".
func pdfHorizontalRules(pdf []byte) map[string]int {
	re := regexp.MustCompile(`([\d.]+) w\s*\n?[^\n]*?([\d.]+) ([\d.]+) m\s*\n?[^\n]*?([\d.]+) ([\d.]+) l`)
	out := map[string]int{}
	for _, m := range re.FindAllStringSubmatch(string(pdf), -1) {
		y1, y2 := m[3], m[5]
		if y1 != y2 {
			continue // not horizontal
		}
		out[y1]++
	}
	return out
}

// The bug: the row loop drew a rule under EVERY row including the last, and then the
// summary block drew its divider at exactly the same y. Two rules at one y read as a
// double border between the table and its summaries.
func TestPDFDrawsOneRuleBetweenTableAndSummaries(t *testing.T) {
	s := newCalcTable()
	s.AddSummaryRow(UserSummaryRow{ID: "t", Label: "Total", Fn: CALC_FN_SUM,
		Operands: []string{"Revenue"}, DataType: CALC_TYPE_NUMBER, Precision: CalcPrecision(0)})
	s.Render()

	rules := pdfHorizontalRules(s.ExportPDFBytes(AutoTablePDFHeader{Title: "R"}))
	if len(rules) == 0 {
		t.Fatal("no horizontal rules found — the extractor is wrong, not the PDF")
	}
	for y, n := range rules {
		if n > 1 {
			t.Errorf("%d rules stacked at y=%s — that is the double border", n, y)
		}
	}
}

// Without summaries the table still gets its closing rule; removing the last row's
// rule must not leave the table open at the bottom.
func TestPDFClosesTheTableWithoutSummaries(t *testing.T) {
	s := newCalcTable()
	s.Render()
	pdf := s.ExportPDFBytes(AutoTablePDFHeader{Title: "R"})

	rules := pdfHorizontalRules(pdf)
	if len(rules) < 2 {
		t.Errorf("expected a header rule and a closing rule, got %d distinct rules", len(rules))
	}
	for y, n := range rules {
		if n > 1 {
			t.Errorf("%d rules stacked at y=%s", n, y)
		}
	}

	// And the lowest rule sits below the last row of text, i.e. the table is closed.
	lowest := 1e9
	for y := range rules {
		if f, err := strconv.ParseFloat(y, 64); err == nil && f < lowest {
			lowest = f
		}
	}
	if lowest > 700 || !strings.Contains(string(pdf), "%%EOF") {
		t.Errorf("closing rule at y=%v looks wrong", lowest)
	}
}

// The extractor must actually see stacked rules, or the two tests above are vacuous.
func TestPDFRuleExtractorCatchesADoubledRule(t *testing.T) {
	pdf := NewPDF(PDFOptions{})
	pdf.Line(50, 300, 500, 300, 0.5, PDFColor{})
	pdf.Line(50, 300, 500, 300, 1, PDFColor{}) // deliberately stacked
	pdf.Line(50, 200, 500, 200, 0.5, PDFColor{})

	rules := pdfHorizontalRules(pdf.Bytes())
	doubled := 0
	for _, n := range rules {
		if n > 1 {
			doubled++
		}
	}
	if doubled != 1 {
		t.Fatalf("extractor found %d stacked positions, want exactly 1 — it cannot see the bug it is meant to catch (%v)", doubled, rules)
	}
}
