package main

import (
	"bytes"
	"context"
	"encoding/json"
	"io"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestParseMaturityScaleLadderRequiresAllowFor1GB(t *testing.T) {
	_, err := parseMaturityConfig([]string{"scale-ladder", "--tier", "1gb"}, io.Discard)
	if err == nil || !strings.Contains(err.Error(), "--allow-1gb") {
		t.Fatalf("parse 1gb without allow = %v, want --allow-1gb error", err)
	}
	_, err = parseMaturityConfig([]string{"scale-ladder", "--tier", "1GB", "--target-bytes", "4096"}, io.Discard)
	if err == nil || !strings.Contains(err.Error(), "--allow-1gb") {
		t.Fatalf("parse mixed-case 1gb without allow = %v, want --allow-1gb error", err)
	}
	config, err := parseMaturityConfig([]string{"scale-ladder", "--tier", "1gb", "--allow-1gb", "--target-bytes", "4096"}, io.Discard)
	if err != nil {
		t.Fatalf("parse 1gb with allow: %v", err)
	}
	if config.Tier != "1gb" || config.TargetBytes != 4096 {
		t.Fatalf("config = %+v, want 1gb target override", config)
	}
}

func TestExecuteScaleLadderMaturityWritesReducedReports(t *testing.T) {
	runRoot := t.TempDir()
	reportDir := t.TempDir()
	config := maturityConfig{
		Mode:        maturityModeScaleLadder,
		Tier:        scaleTier10MB,
		TargetBytes: 50 * 1024,
		Seed:        7,
		RunRoot:     runRoot,
		ReportDir:   reportDir,
		ReportName:  "test-scale-ladder",
		SkipReopen:  true,
	}
	var stdout bytes.Buffer
	if err := executeMaturity(context.Background(), config, &stdout); err != nil {
		t.Fatalf("execute maturity: %v", err)
	}
	jsonBytes := readReportForTest(t, filepath.Join(reportDir, "test-scale-ladder.json"))
	markdownBytes := readReportForTest(t, filepath.Join(reportDir, "test-scale-ladder.md"))
	assertReducedReportForTest(t, string(jsonBytes), runRoot)
	assertReducedReportForTest(t, string(markdownBytes), runRoot)

	var rep maturityReport
	if err := json.Unmarshal(jsonBytes, &rep); err != nil {
		t.Fatalf("decode maturity report: %v", err)
	}
	if rep.Metadata.RunRootArtifactReference != "<run-root>" {
		t.Fatalf("run root reference = %q", rep.Metadata.RunRootArtifactReference)
	}
	if rep.Corpus.GeneratedBytes < config.TargetBytes {
		t.Fatalf("generated bytes = %d, want at least %d", rep.Corpus.GeneratedBytes, config.TargetBytes)
	}
	if rep.Corpus.Documents == 0 || rep.Corpus.SourceDocuments == 0 || rep.Corpus.SynthesisDocuments == 0 || rep.Corpus.DecisionDocuments == 0 {
		t.Fatalf("corpus counts missing expected generated mix: %+v", rep.Corpus)
	}
	if rep.Checks.RawContentCommitted || rep.Checks.MachineAbsoluteArtifactRefs {
		t.Fatalf("unsafe report checks: %+v", rep.Checks)
	}
	if rep.SyncDiagnostics.ImportSync.PathsScanned == 0 || rep.SyncDiagnostics.ImportSync.DocumentsCreated == 0 {
		t.Fatalf("missing import sync diagnostics: %+v", rep.SyncDiagnostics.ImportSync)
	}
	if rep.SyncDiagnostics.ImportSync.FTSStrategy == "" || rep.SyncDiagnostics.ImportSync.FTSRowsWritten == 0 {
		t.Fatalf("missing FTS sync diagnostics: %+v", rep.SyncDiagnostics.ImportSync)
	}
	if !strings.Contains(string(markdownBytes), "bulk_fts_rebuild_seconds") {
		t.Fatalf("maturity Markdown did not include FTS timing diagnostics: %s", markdownBytes)
	}
	if !rep.SyncDiagnostics.ImportSync.ReducedReportSafe {
		t.Fatalf("sync diagnostics not marked reduced-report safe: %+v", rep.SyncDiagnostics.ImportSync)
	}
}

func TestExecuteRealVaultMaturityRedactsPrivateInputs(t *testing.T) {
	runRoot := t.TempDir()
	reportDir := t.TempDir()
	privateVault := filepath.Join(t.TempDir(), "private-vault")
	if err := os.MkdirAll(privateVault, 0o755); err != nil {
		t.Fatalf("create private vault: %v", err)
	}
	privatePhrase := "PrivateQueryNeedleForReducedReport"
	body := "---\ntype: source\nstatus: active\ntag: private-test\n---\n# Private Test\n\n" + privatePhrase + " should be searchable but not emitted.\n"
	if err := os.WriteFile(filepath.Join(privateVault, "private.md"), []byte(body), 0o644); err != nil {
		t.Fatalf("write private doc: %v", err)
	}
	config := maturityConfig{
		Mode:             maturityModeRealVault,
		RunRoot:          runRoot,
		ReportDir:        reportDir,
		ReportName:       "test-real-vault",
		PrivateVaultRoot: privateVault,
		QueryCSV:         privatePhrase,
		SkipReopen:       true,
	}
	var stdout bytes.Buffer
	if err := executeMaturity(context.Background(), config, &stdout); err != nil {
		t.Fatalf("execute real-vault maturity: %v", err)
	}
	jsonContent := string(readReportForTest(t, filepath.Join(runRoot, "test-real-vault.json")))
	markdownContent := string(readReportForTest(t, filepath.Join(reportDir, "test-real-vault.md")))
	if _, err := os.Stat(filepath.Join(reportDir, "test-real-vault.json")); !os.IsNotExist(err) {
		t.Fatalf("real-vault JSON report should stay under run root, got err %v", err)
	}
	for _, content := range []string{jsonContent, markdownContent} {
		assertReducedReportForTest(t, content, runRoot)
		assertReducedReportForTest(t, content, privateVault)
		if strings.Contains(content, privatePhrase) {
			t.Fatalf("reduced report leaked private query/content: %s", content)
		}
	}
	if !strings.Contains(jsonContent, "private-query-1") {
		t.Fatalf("real-vault report did not use neutral private query reference: %s", jsonContent)
	}
	if !strings.Contains(markdownContent, "## Sync Diagnostics") {
		t.Fatalf("real-vault report did not include sync diagnostics: %s", markdownContent)
	}
}

func readReportForTest(t *testing.T, path string) []byte {
	t.Helper()
	content, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read report %s: %v", path, err)
	}
	return content
}

func assertReducedReportForTest(t *testing.T, content string, localPath string) {
	t.Helper()
	if strings.Contains(content, localPath) || strings.Contains(content, filepath.ToSlash(localPath)) {
		t.Fatalf("reduced report leaked local path %q: %s", localPath, content)
	}
}
