package infra

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

func writeTestFile(t *testing.T, dir, rel, content string) {
	t.Helper()
	full := filepath.Join(dir, filepath.FromSlash(rel))
	if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
		t.Fatalf("mkdir %s: %v", filepath.Dir(full), err)
	}
	if err := os.WriteFile(full, []byte(content), 0o644); err != nil {
		t.Fatalf("write %s: %v", full, err)
	}
}

func TestVaultInventoryScan_Empty(t *testing.T) {
	t.Run("tmpdir vacio retorna slice vacio", func(t *testing.T) {
		dir := t.TempDir()
		files, err := VaultInventoryScan(dir, "v1", "test")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(files) != 0 {
			t.Errorf("expected 0 files, got %d", len(files))
		}
	})
}

func TestVaultInventoryScan_DataLayout(t *testing.T) {
	t.Run("data layout — bucket y sub_bucket correctos", func(t *testing.T) {
		dir := t.TempDir()
		writeTestFile(t, dir, "data/raw/a.csv", "col1,col2\n1,2\n")
		writeTestFile(t, dir, "data/processed/b.parquet", "PAR1fakedata")

		files, err := VaultInventoryScan(dir, "vid", "vname")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(files) != 2 {
			t.Fatalf("expected 2 files, got %d", len(files))
		}

		// files are sorted: data/processed/b.parquet < data/raw/a.csv
		b := files[0]
		if b.RelPath != "data/processed/b.parquet" {
			t.Errorf("files[0].RelPath = %q, want data/processed/b.parquet", b.RelPath)
		}
		if b.Bucket != "data" {
			t.Errorf("files[0].Bucket = %q, want data", b.Bucket)
		}
		if b.SubBucket != "processed" {
			t.Errorf("files[0].SubBucket = %q, want processed", b.SubBucket)
		}
		if b.Mime != "application/parquet" {
			t.Errorf("files[0].Mime = %q, want application/parquet", b.Mime)
		}
		if b.Ext != ".parquet" {
			t.Errorf("files[0].Ext = %q, want .parquet", b.Ext)
		}
		if b.VaultID != "vid" {
			t.Errorf("VaultID = %q, want vid", b.VaultID)
		}

		a := files[1]
		if a.RelPath != "data/raw/a.csv" {
			t.Errorf("files[1].RelPath = %q, want data/raw/a.csv", a.RelPath)
		}
		if a.Mime != "text/csv" {
			t.Errorf("files[1].Mime = %q, want text/csv", a.Mime)
		}
		if a.Bucket != "data" || a.SubBucket != "raw" {
			t.Errorf("files[1]: bucket=%q subBucket=%q, want data/raw", a.Bucket, a.SubBucket)
		}
	})
}

func TestVaultInventoryScan_KnowledgeLayout(t *testing.T) {
	t.Run("knowledge layout — bucket y sub_bucket correctos", func(t *testing.T) {
		dir := t.TempDir()
		writeTestFile(t, dir, "knowledge/decisions/x.md", "# Decision\n\ncontent")

		files, err := VaultInventoryScan(dir, "vid", "vname")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(files) != 1 {
			t.Fatalf("expected 1 file, got %d", len(files))
		}
		f := files[0]
		if f.RelPath != "knowledge/decisions/x.md" {
			t.Errorf("RelPath = %q", f.RelPath)
		}
		if f.Bucket != "knowledge" {
			t.Errorf("Bucket = %q, want knowledge", f.Bucket)
		}
		if f.SubBucket != "decisions" {
			t.Errorf("SubBucket = %q, want decisions", f.SubBucket)
		}
		if f.Mime != "text/markdown" {
			t.Errorf("Mime = %q, want text/markdown", f.Mime)
		}
	})
}

func TestVaultInventoryScan_SkipsIndexAndGit(t *testing.T) {
	t.Run("omite vault_index.db y .git", func(t *testing.T) {
		dir := t.TempDir()
		writeTestFile(t, dir, "vault_index.db", "sqlite data")
		writeTestFile(t, dir, "vault_index.db-wal", "wal data")
		writeTestFile(t, dir, ".git/HEAD", "ref: refs/heads/master")
		writeTestFile(t, dir, "data/raw/real.csv", "a,b\n1,2\n")

		files, err := VaultInventoryScan(dir, "vid", "vname")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(files) != 1 {
			t.Fatalf("expected 1 file (real.csv), got %d: %v", len(files), relPaths(files))
		}
		if files[0].RelPath != "data/raw/real.csv" {
			t.Errorf("unexpected file: %q", files[0].RelPath)
		}
	})
}

func TestVaultInventoryScan_Sha256Deterministic(t *testing.T) {
	t.Run("sha256 determinista para mismo contenido", func(t *testing.T) {
		dir1 := t.TempDir()
		dir2 := t.TempDir()
		content := "deterministic content 123\n"
		writeTestFile(t, dir1, "data/raw/f.csv", content)
		writeTestFile(t, dir2, "data/raw/f.csv", content)

		files1, err := VaultInventoryScan(dir1, "v1", "vault1")
		if err != nil {
			t.Fatal(err)
		}
		files2, err := VaultInventoryScan(dir2, "v2", "vault2")
		if err != nil {
			t.Fatal(err)
		}
		if files1[0].Sha256 != files2[0].Sha256 {
			t.Errorf("sha256 mismatch: %q vs %q", files1[0].Sha256, files2[0].Sha256)
		}
		if len(files1[0].Sha256) != 64 {
			t.Errorf("sha256 length = %d, want 64", len(files1[0].Sha256))
		}
	})
}

func TestVaultInventoryScan_Sorted(t *testing.T) {
	t.Run("orden lexicografico del resultado", func(t *testing.T) {
		dir := t.TempDir()
		writeTestFile(t, dir, "knowledge/decisions/z.md", "z")
		writeTestFile(t, dir, "data/raw/a.csv", "a")
		writeTestFile(t, dir, "data/processed/m.parquet", "m")
		writeTestFile(t, dir, "knowledge/domains/b.md", "b")

		files, err := VaultInventoryScan(dir, "v", "v")
		if err != nil {
			t.Fatal(err)
		}
		for i := 1; i < len(files); i++ {
			if files[i].RelPath < files[i-1].RelPath {
				t.Errorf("not sorted at index %d: %q < %q", i, files[i].RelPath, files[i-1].RelPath)
			}
		}
	})
}

// relPaths is a helper for test error messages.
func relPaths(files []VaultFile) []string {
	out := make([]string, len(files))
	for i, f := range files {
		out[i] = f.RelPath
	}
	return out
}
