package infra

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

// setupDoctorRepo creates a repo layout with one vault in a project manifest.
// vaultPath must be an absolute path that already exists (or not, for missing tests).
func setupDoctorRepo(t *testing.T, vaultName, projectID, vaultPath string) string {
	t.Helper()
	root := t.TempDir()
	projVaultsDir := filepath.Join(root, "projects", projectID, "vaults")
	if err := os.MkdirAll(projVaultsDir, 0755); err != nil {
		t.Fatalf("mkdir projects: %v", err)
	}
	manifest := "vaults:\n  - name: " + vaultName + "\n    description: test vault\n    path: " + vaultPath + "\n    tags: []\n"
	if err := os.WriteFile(filepath.Join(projVaultsDir, "vault.yaml"), []byte(manifest), 0644); err != nil {
		t.Fatalf("write vault.yaml: %v", err)
	}
	return root
}

func TestVaultDoctor_OK(t *testing.T) {
	vaultDir := t.TempDir()

	// Proper layout
	if err := os.MkdirAll(filepath.Join(vaultDir, "data", "raw"), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Join(vaultDir, "knowledge"), 0755); err != nil {
		t.Fatal(err)
	}

	// Create a file with a past mtime so the index is not stale
	samplePath := filepath.Join(vaultDir, "data", "raw", "sample.csv")
	if err := os.WriteFile(samplePath, []byte("a,b\n1,2\n"), 0644); err != nil {
		t.Fatal(err)
	}
	pastTime := time.Now().Add(-1 * time.Hour)
	if err := os.Chtimes(samplePath, pastTime, pastTime); err != nil {
		t.Fatal(err)
	}

	// Create vault_index.db with the file indexed after its mtime
	vdb, err := VaultIndexOpen(vaultDir)
	if err != nil {
		t.Fatalf("VaultIndexOpen: %v", err)
	}
	futureIndexed := time.Now().Unix() // indexed_at is now — after file mtime
	_, err = vdb.Exec(`INSERT INTO files (rel_path, size, mtime, sha256, mime, ext, bucket, sub_bucket, indexed_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		"data/raw/sample.csv", 8, pastTime.Unix(), "deadbeef", "text/csv", ".csv", "data", "raw", futureIndexed)
	if err != nil {
		t.Fatalf("insert: %v", err)
	}
	vdb.Close()

	root := setupDoctorRepo(t, "my_vault", "my_proj", vaultDir)
	entries, err := VaultDoctor(root)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	e := entries[0]
	if e.Status != "ok" {
		t.Errorf("Status: want ok, got %s (issues: %v)", e.Status, e.Issues)
	}
	if len(e.Issues) != 0 {
		t.Errorf("Issues: want empty, got %v", e.Issues)
	}
	if e.DiskFiles != 1 {
		t.Errorf("DiskFiles: want 1, got %d", e.DiskFiles)
	}
	if e.IndexedFiles != 1 {
		t.Errorf("IndexedFiles: want 1, got %d", e.IndexedFiles)
	}
}

func TestVaultDoctor_MissingDir(t *testing.T) {
	missingPath := filepath.Join(t.TempDir(), "does_not_exist")
	root := setupDoctorRepo(t, "missing_vault", "my_proj", missingPath)

	entries, err := VaultDoctor(root)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	e := entries[0]
	if e.Status != "error" {
		t.Errorf("Status: want error, got %s", e.Status)
	}
	found := false
	for _, issue := range e.Issues {
		if issue == "directory_missing" {
			found = true
		}
	}
	if !found {
		t.Errorf("Expected directory_missing issue, got %v", e.Issues)
	}
}

func TestVaultDoctor_NoIndex(t *testing.T) {
	vaultDir := t.TempDir()
	// Proper layout but no vault_index.db
	if err := os.MkdirAll(filepath.Join(vaultDir, "data", "raw"), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(vaultDir, "data", "raw", "a.csv"), []byte("x"), 0644); err != nil {
		t.Fatal(err)
	}

	root := setupDoctorRepo(t, "no_index_vault", "my_proj", vaultDir)
	entries, err := VaultDoctor(root)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	e := entries[0]
	if e.Status != "warning" {
		t.Errorf("Status: want warning, got %s", e.Status)
	}
	found := false
	for _, issue := range e.Issues {
		if issue == "index_missing" {
			found = true
		}
	}
	if !found {
		t.Errorf("Expected index_missing issue, got %v", e.Issues)
	}
}

func TestVaultDoctor_LayoutDrift(t *testing.T) {
	vaultDir := t.TempDir()
	// No data/ or knowledge/ — just a random file at root
	if err := os.WriteFile(filepath.Join(vaultDir, "something.txt"), []byte("hi"), 0644); err != nil {
		t.Fatal(err)
	}

	root := setupDoctorRepo(t, "layout_vault", "my_proj", vaultDir)
	entries, err := VaultDoctor(root)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	e := entries[0]
	if e.Status != "warning" {
		t.Errorf("Status: want warning, got %s", e.Status)
	}
	foundLayout := false
	for _, issue := range e.Issues {
		if issue == "layout_missing" || issue == "non_standard_layout" {
			foundLayout = true
		}
	}
	if !foundLayout {
		t.Errorf("Expected layout_missing or non_standard_layout, got %v", e.Issues)
	}
}

func TestVaultDoctor_EmptyVault(t *testing.T) {
	vaultDir := t.TempDir()
	// data/ and knowledge/ exist but are empty
	if err := os.MkdirAll(filepath.Join(vaultDir, "data"), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Join(vaultDir, "knowledge"), 0755); err != nil {
		t.Fatal(err)
	}

	// Create vault_index.db (empty)
	vdb, err := VaultIndexOpen(vaultDir)
	if err != nil {
		t.Fatalf("VaultIndexOpen: %v", err)
	}
	vdb.Close()

	root := setupDoctorRepo(t, "empty_vault", "my_proj", vaultDir)
	entries, err := VaultDoctor(root)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(entries) != 1 {
		t.Fatalf("expected 1 entry, got %d", len(entries))
	}
	e := entries[0]
	if e.Status != "warning" {
		t.Errorf("Status: want warning, got %s (issues: %v)", e.Status, e.Issues)
	}
	found := false
	for _, issue := range e.Issues {
		if issue == "empty_vault" {
			found = true
		}
	}
	if !found {
		t.Errorf("Expected empty_vault issue, got %v", e.Issues)
	}
}
