package infra

import (
	"database/sql"
	"os"
	"path/filepath"
	"testing"

	_ "github.com/mattn/go-sqlite3"
)

// setupTestRegistry creates a minimal registry.db with pc_locations + apps tables
// in a temp directory and returns the root path + a cleanup func.
func setupTestRegistry(t *testing.T) (string, func()) {
	t.Helper()
	root := t.TempDir()

	dbPath := filepath.Join(root, "registry.db")
	db, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		t.Fatalf("open test db: %v", err)
	}
	defer db.Close()

	_, err = db.Exec(`
		CREATE TABLE pc_locations (
			id          TEXT PRIMARY KEY,
			entity_type TEXT NOT NULL,
			entity_id   TEXT NOT NULL,
			pc_id       TEXT NOT NULL,
			dir_path    TEXT NOT NULL,
			status      TEXT NOT NULL,
			notes       TEXT,
			created_at  TEXT,
			updated_at  TEXT
		);
		CREATE TABLE apps (
			id       TEXT PRIMARY KEY,
			name     TEXT,
			dir_path TEXT NOT NULL DEFAULT ''
		);
		CREATE TABLE analysis (
			id       TEXT PRIMARY KEY,
			name     TEXT,
			dir_path TEXT NOT NULL DEFAULT ''
		);
	`)
	if err != nil {
		t.Fatalf("create schema: %v", err)
	}

	return root, func() {}
}

func insertLocation(t *testing.T, root, entityType, entityID, dirPath, status, pcID string) {
	t.Helper()
	db, _ := sql.Open("sqlite3", filepath.Join(root, "registry.db"))
	defer db.Close()
	id := entityType + "_" + entityID + "_" + pcID
	_, err := db.Exec(
		`INSERT INTO pc_locations(id, entity_type, entity_id, pc_id, dir_path, status) VALUES (?,?,?,?,?,?)`,
		id, entityType, entityID, pcID, dirPath, status,
	)
	if err != nil {
		t.Fatalf("insert location: %v", err)
	}
}

func insertApp(t *testing.T, root, id, dirPath string) {
	t.Helper()
	db, _ := sql.Open("sqlite3", filepath.Join(root, "registry.db"))
	defer db.Close()
	_, err := db.Exec(`INSERT INTO apps(id, dir_path) VALUES (?,?)`, id, dirPath)
	if err != nil {
		t.Fatalf("insert app: %v", err)
	}
}

func TestPcLocationsDrift_DetectsMissingFolder(t *testing.T) {
	root, cleanup := setupTestRegistry(t)
	defer cleanup()

	// An active entry whose folder does NOT exist on disk
	insertLocation(t, root, "app", "my_app", "apps/my_app", "active", "test-pc")

	drifts, err := PcLocationsDrift(root, "test-pc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	found := false
	for _, d := range drifts {
		if d.EntityID == "my_app" && d.Issue == "missing_on_disk" {
			found = true
		}
	}
	if !found {
		t.Errorf("expected missing_on_disk drift for my_app, got: %+v", drifts)
	}
}

func TestPcLocationsDrift_ActiveFolderExistsNoDrift(t *testing.T) {
	root, cleanup := setupTestRegistry(t)
	defer cleanup()

	// Create the folder
	appDir := filepath.Join(root, "apps", "good_app")
	if err := os.MkdirAll(appDir, 0755); err != nil {
		t.Fatal(err)
	}
	insertLocation(t, root, "app", "good_app", "apps/good_app", "active", "test-pc")

	drifts, err := PcLocationsDrift(root, "test-pc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	for _, d := range drifts {
		if d.EntityID == "good_app" {
			t.Errorf("unexpected drift for good_app: %+v", d)
		}
	}
}

func TestPcLocationsDrift_StatusShouldBeActive(t *testing.T) {
	root, cleanup := setupTestRegistry(t)
	defer cleanup()

	// Folder exists but status is "missing"
	appDir := filepath.Join(root, "apps", "came_back")
	if err := os.MkdirAll(appDir, 0755); err != nil {
		t.Fatal(err)
	}
	insertLocation(t, root, "app", "came_back", "apps/came_back", "missing", "test-pc")

	drifts, err := PcLocationsDrift(root, "test-pc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	found := false
	for _, d := range drifts {
		if d.EntityID == "came_back" && d.Issue == "status_should_be_active" {
			found = true
		}
	}
	if !found {
		t.Errorf("expected status_should_be_active for came_back, got: %+v", drifts)
	}
}

func TestPcLocationsDrift_UntrackedOnDisk(t *testing.T) {
	root, cleanup := setupTestRegistry(t)
	defer cleanup()

	// App indexed in registry with folder on disk but no pc_locations entry
	appDir := filepath.Join(root, "apps", "orphan_app")
	if err := os.MkdirAll(appDir, 0755); err != nil {
		t.Fatal(err)
	}
	insertApp(t, root, "orphan_app", "apps/orphan_app")
	// No pc_locations entry for test-pc

	drifts, err := PcLocationsDrift(root, "test-pc")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	found := false
	for _, d := range drifts {
		if d.EntityID == "orphan_app" && d.Issue == "untracked_on_disk" {
			found = true
		}
	}
	if !found {
		t.Errorf("expected untracked_on_disk for orphan_app, got: %+v", drifts)
	}
}
