package infra

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

// helper: write a file under tmp/dev/issues or tmp/dev/flows with given content.
func writeMD(t *testing.T, dir, name, body string) string {
	t.Helper()
	if err := os.MkdirAll(dir, 0o755); err != nil {
		t.Fatalf("mkdir %s: %v", dir, err)
	}
	p := filepath.Join(dir, name)
	if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
		t.Fatalf("write %s: %v", p, err)
	}
	return p
}

func TestDodSchema_Valid(t *testing.T) {
	tmp := t.TempDir()
	issues := filepath.Join(tmp, "dev", "issues")
	flows := filepath.Join(tmp, "dev", "flows")

	writeMD(t, issues, "0200-ok.md", `---
id: "0200"
title: ok
dod_evidence_schema:
  - id: surface_ui
    kind: screenshot
    expected: "kanban shows new card"
    required: true
  - id: backend_health
    kind: cmd
    expected: "curl -fsS http://localhost:8000/api/health"
    required: false
---
body
`)

	report, err := AuditDodSchema(issues, flows)
	if err != nil {
		t.Fatalf("AuditDodSchema: %v", err)
	}
	if report.TotalFiles != 1 {
		t.Fatalf("TotalFiles = %d, want 1", report.TotalFiles)
	}
	if report.FilesWithItems != 1 {
		t.Fatalf("FilesWithItems = %d, want 1", report.FilesWithItems)
	}
	if report.TotalItems != 2 {
		t.Fatalf("TotalItems = %d, want 2", report.TotalItems)
	}
	if report.InvalidItems != 0 {
		t.Fatalf("InvalidItems = %d, want 0; errors=%v", report.InvalidItems, report.Files[0].Errors)
	}
	f := report.Files[0]
	if f.Type != "issue" {
		t.Errorf("Type = %q, want issue", f.Type)
	}
	if f.Items[0].ID != "surface_ui" || f.Items[0].Kind != "screenshot" {
		t.Errorf("item[0] = %+v", f.Items[0])
	}
	if !f.Items[0].Required {
		t.Errorf("item[0].Required = false, want true")
	}
	if f.Items[1].Required {
		t.Errorf("item[1].Required = true, want false")
	}
}

func TestDodSchema_InvalidKind(t *testing.T) {
	tmp := t.TempDir()
	issues := filepath.Join(tmp, "dev", "issues")
	writeMD(t, issues, "0201-badkind.md", `---
id: "0201"
dod_evidence_schema:
  - id: bad
    kind: png
    expected: "nope"
---
`)
	report, _ := AuditDodSchema(issues, "")
	if len(report.Files) != 1 {
		t.Fatalf("want 1 file, got %d", len(report.Files))
	}
	errs := strings.Join(report.Files[0].Errors, "|")
	if !strings.Contains(errs, "invalid kind 'png'") {
		t.Errorf("expected invalid kind error, got %q", errs)
	}
	if report.InvalidItems != 1 {
		t.Errorf("InvalidItems = %d, want 1", report.InvalidItems)
	}
}

func TestDodSchema_DuplicateID(t *testing.T) {
	tmp := t.TempDir()
	flows := filepath.Join(tmp, "dev", "flows")
	writeMD(t, flows, "0001-dup.md", `---
name: dup
dod_evidence_schema:
  - id: surface_1
    kind: url
    expected: "open dashboard"
  - id: surface_1
    kind: cmd
    expected: "other"
---
`)
	report, _ := AuditDodSchema("", flows)
	errs := strings.Join(report.Files[0].Errors, "|")
	if !strings.Contains(errs, "duplicate id") {
		t.Errorf("expected duplicate id error, got %q", errs)
	}
	if report.Files[0].Type != "flow" {
		t.Errorf("Type = %q, want flow", report.Files[0].Type)
	}
}

func TestDodSchema_EmptyExpected(t *testing.T) {
	tmp := t.TempDir()
	issues := filepath.Join(tmp, "dev", "issues")
	writeMD(t, issues, "0202-noexpect.md", `---
dod_evidence_schema:
  - id: empty
    kind: log
    expected: ""
---
`)
	report, _ := AuditDodSchema(issues, "")
	errs := strings.Join(report.Files[0].Errors, "|")
	if !strings.Contains(errs, "empty expected") {
		t.Errorf("expected empty expected error, got %q", errs)
	}
}

func TestDodSchema_MalformedYAML(t *testing.T) {
	tmp := t.TempDir()
	issues := filepath.Join(tmp, "dev", "issues")
	writeMD(t, issues, "0203-bad.md", `---
id: "0203
dod_evidence_schema:
  - id: x
    kind: cmd
    expected: nope
---
`)
	report, err := AuditDodSchema(issues, "")
	if err != nil {
		t.Fatalf("AuditDodSchema returned err on malformed: %v", err)
	}
	if len(report.Files) != 1 {
		t.Fatalf("want 1 file, got %d", len(report.Files))
	}
	errs := strings.Join(report.Files[0].Errors, "|")
	if !strings.Contains(errs, "malformed") {
		t.Errorf("expected malformed error, got %q", errs)
	}
}

func TestDodSchema_NoBlock(t *testing.T) {
	tmp := t.TempDir()
	issues := filepath.Join(tmp, "dev", "issues")
	writeMD(t, issues, "0204-noblock.md", `---
id: "0204"
title: no schema here
---
body
`)
	report, _ := AuditDodSchema(issues, "")
	if len(report.Files) != 1 {
		t.Fatalf("want 1 file, got %d", len(report.Files))
	}
	if len(report.Files[0].Items) != 0 {
		t.Errorf("Items=%d, want 0", len(report.Files[0].Items))
	}
	if len(report.Files[0].Errors) != 0 {
		t.Errorf("Errors=%v, want none", report.Files[0].Errors)
	}
	if report.FilesWithItems != 0 {
		t.Errorf("FilesWithItems = %d, want 0", report.FilesWithItems)
	}
}

func TestDodSchema_SkipsConventionFiles(t *testing.T) {
	tmp := t.TempDir()
	issues := filepath.Join(tmp, "dev", "issues")
	// README/INDEX/template/AGENT_GUIDE should be skipped even if they have a dod_evidence_schema block.
	writeMD(t, issues, "README.md", `---
dod_evidence_schema:
  - id: x
    kind: png
    expected: ""
---
`)
	writeMD(t, issues, "0205-real.md", `---
id: "0205"
---
`)
	report, _ := AuditDodSchema(issues, "")
	if report.TotalFiles != 1 {
		t.Fatalf("TotalFiles=%d, want 1 (README must be skipped)", report.TotalFiles)
	}
	if !strings.HasSuffix(report.Files[0].Path, "0205-real.md") {
		t.Errorf("unexpected file scanned: %s", report.Files[0].Path)
	}
}

func TestDodSchema_RecurseCompleted(t *testing.T) {
	tmp := t.TempDir()
	issues := filepath.Join(tmp, "dev", "issues")
	writeMD(t, filepath.Join(issues, "completed"), "0206-done.md", `---
dod_evidence_schema:
  - id: a
    kind: url
    expected: "http://localhost"
---
`)
	report, _ := AuditDodSchema(issues, "")
	if report.TotalFiles != 1 {
		t.Fatalf("TotalFiles=%d, want 1 (completed/ must be walked)", report.TotalFiles)
	}
	if report.TotalItems != 1 {
		t.Errorf("TotalItems=%d, want 1", report.TotalItems)
	}
}
