package infra

import (
	"testing"
	"time"
)

// makeTestVaultFile creates a minimal VaultFile for testing.
func makeTestVaultFile(relPath, mime, bucket, subBucket string) VaultFile {
	return VaultFile{
		VaultID:   "test_vault",
		VaultName: "test",
		RelPath:   relPath,
		Size:      100,
		Mtime:     time.Now().Unix(),
		Sha256:    "abc123def456abc123def456abc123def456abc123def456abc123def456abc1",
		Mime:      mime,
		Ext:       ".csv",
		Bucket:    bucket,
		SubBucket: subBucket,
	}
}

func openInMemoryVaultIndex(t *testing.T) interface{ Close() error } {
	t.Helper()
	dir := t.TempDir()
	db, err := VaultIndexOpen(dir)
	if err != nil {
		t.Fatalf("VaultIndexOpen: %v", err)
	}
	return db
}

func TestVaultIndexWrite_FreshInsert(t *testing.T) {
	t.Run("N archivos nuevos — Inserted=N", func(t *testing.T) {
		dir := t.TempDir()
		db, err := VaultIndexOpen(dir)
		if err != nil {
			t.Fatal(err)
		}
		defer db.Close()

		files := []VaultFile{
			makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"),
			makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"),
			makeTestVaultFile("knowledge/decisions/x.md", "text/markdown", "knowledge", "decisions"),
		}

		report, err := VaultIndexWrite(db, files, false)
		if err != nil {
			t.Fatalf("VaultIndexWrite: %v", err)
		}
		if report.Inserted != 3 {
			t.Errorf("Inserted = %d, want 3", report.Inserted)
		}
		if report.Updated != 0 {
			t.Errorf("Updated = %d, want 0", report.Updated)
		}
		if report.Pruned != 0 {
			t.Errorf("Pruned = %d, want 0", report.Pruned)
		}
		if report.FTS != 3 {
			t.Errorf("FTS = %d, want 3", report.FTS)
		}
	})
}

func TestVaultIndexWrite_Upsert(t *testing.T) {
	t.Run("re-escritura con mtime distinto — Updated=N", func(t *testing.T) {
		dir := t.TempDir()
		db, err := VaultIndexOpen(dir)
		if err != nil {
			t.Fatal(err)
		}
		defer db.Close()

		files := []VaultFile{
			makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"),
			makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"),
		}

		if _, err := VaultIndexWrite(db, files, false); err != nil {
			t.Fatalf("first write: %v", err)
		}

		// Modify mtime to simulate file change.
		files[0].Mtime = time.Now().Unix() + 100
		files[1].Mtime = time.Now().Unix() + 200

		report, err := VaultIndexWrite(db, files, false)
		if err != nil {
			t.Fatalf("second write: %v", err)
		}
		if report.Inserted != 0 {
			t.Errorf("Inserted = %d, want 0", report.Inserted)
		}
		if report.Updated != 2 {
			t.Errorf("Updated = %d, want 2", report.Updated)
		}
	})
}

func TestVaultIndexWrite_Prune(t *testing.T) {
	t.Run("prune elimina filas ausentes", func(t *testing.T) {
		dir := t.TempDir()
		db, err := VaultIndexOpen(dir)
		if err != nil {
			t.Fatal(err)
		}
		defer db.Close()

		// Write A and B.
		ab := []VaultFile{
			makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"),
			makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"),
		}
		if _, err := VaultIndexWrite(db, ab, false); err != nil {
			t.Fatalf("first write: %v", err)
		}

		// Write only A with prune=true — B should be deleted.
		onlyA := []VaultFile{ab[0]}
		report, err := VaultIndexWrite(db, onlyA, true)
		if err != nil {
			t.Fatalf("prune write: %v", err)
		}
		if report.Pruned != 1 {
			t.Errorf("Pruned = %d, want 1", report.Pruned)
		}

		// Verify B is gone.
		var count int
		err = db.QueryRow(`SELECT count(*) FROM files WHERE rel_path = 'data/raw/b.csv'`).Scan(&count)
		if err != nil {
			t.Fatalf("query: %v", err)
		}
		if count != 0 {
			t.Errorf("b.csv still present after prune")
		}
	})
}

func TestVaultIndexWrite_NoPrune(t *testing.T) {
	t.Run("sin prune, filas previas persisten", func(t *testing.T) {
		dir := t.TempDir()
		db, err := VaultIndexOpen(dir)
		if err != nil {
			t.Fatal(err)
		}
		defer db.Close()

		ab := []VaultFile{
			makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"),
			makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"),
		}
		if _, err := VaultIndexWrite(db, ab, false); err != nil {
			t.Fatalf("first write: %v", err)
		}

		// Write only A without prune — B must remain.
		onlyA := []VaultFile{ab[0]}
		report, err := VaultIndexWrite(db, onlyA, false)
		if err != nil {
			t.Fatalf("second write: %v", err)
		}
		if report.Pruned != 0 {
			t.Errorf("Pruned = %d, want 0", report.Pruned)
		}

		var count int
		err = db.QueryRow(`SELECT count(*) FROM files`).Scan(&count)
		if err != nil {
			t.Fatalf("query: %v", err)
		}
		if count != 2 {
			t.Errorf("files count = %d, want 2", count)
		}
	})
}

func TestVaultIndexWrite_FTSMatch(t *testing.T) {
	t.Run("FTS5 MATCH funciona tras escritura", func(t *testing.T) {
		dir := t.TempDir()
		db, err := VaultIndexOpen(dir)
		if err != nil {
			t.Fatal(err)
		}
		defer db.Close()

		files := []VaultFile{
			makeTestVaultFile("data/raw/foo_report.csv", "text/csv", "data", "raw"),
			makeTestVaultFile("data/raw/bar_data.csv", "text/csv", "data", "raw"),
		}
		if _, err := VaultIndexWrite(db, files, false); err != nil {
			t.Fatalf("write: %v", err)
		}

		// FTS5 on rel_path column: MATCH 'foo*'
		var count int
		err = db.QueryRow(
			`SELECT count(*) FROM files_fts WHERE files_fts MATCH 'rel_path:foo*'`,
		).Scan(&count)
		if err != nil {
			t.Fatalf("FTS MATCH query: %v", err)
		}
		if count != 1 {
			t.Errorf("FTS MATCH rel_path:foo* = %d rows, want 1", count)
		}
	})
}
