package infra

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

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

// createTestRegistryDB creates a minimal registry.db with the given apps and
// a single function (random_hex_id_go_core in domain core, lang go).
func createTestRegistryDB(t *testing.T, root string, apps []struct {
	id            string
	lang          string
	dirPath       string
	usesFunctions string
}) {
	t.Helper()
	dbPath := filepath.Join(root, "registry.db")
	db, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	_, err = db.Exec(`
CREATE TABLE functions (
	id TEXT PRIMARY KEY,
	name TEXT,
	domain TEXT,
	lang TEXT,
	signature TEXT,
	file_path TEXT
);
CREATE TABLE apps (
	id TEXT PRIMARY KEY,
	lang TEXT,
	dir_path TEXT,
	uses_functions TEXT DEFAULT '[]'
);
INSERT INTO functions (id, name, domain, lang, file_path)
	VALUES ('random_hex_id_go_core','random_hex_id','core','go','functions/core/random_hex_id.go');
`)
	if err != nil {
		t.Fatal(err)
	}
	for _, a := range apps {
		_, err = db.Exec(
			`INSERT INTO apps (id, lang, dir_path, uses_functions) VALUES (?,?,?,?)`,
			a.id, a.lang, a.dirPath, a.usesFunctions,
		)
		if err != nil {
			t.Fatalf("insert app %s: %v", a.id, err)
		}
	}
}

// TestAuditUsesFunctions_DetectsMissing verifies that a Go app that calls
// RandomHexID in its source but declares empty uses_functions gets
// random_hex_id_go_core reported as missing.
func TestAuditUsesFunctions_DetectsMissing(t *testing.T) {
	t.Run("missing function detected for Go app", func(t *testing.T) {
		root := t.TempDir()
		createTestRegistryDB(t, root, []struct {
			id, lang, dirPath, usesFunctions string
		}{
			{"testapp_go_tools", "go", "apps/testapp", `[]`},
		})

		appDir := filepath.Join(root, "apps", "testapp")
		if err := os.MkdirAll(appDir, 0755); err != nil {
			t.Fatal(err)
		}
		goSrc := `package main

import (
	"fmt"
	"fn-registry/functions/core"
)

func main() {
	id := core.RandomHexID(8)
	fmt.Println(id)
}
`
		if err := os.WriteFile(filepath.Join(appDir, "main.go"), []byte(goSrc), 0644); err != nil {
			t.Fatal(err)
		}

		results, err := AuditUsesFunctions(root)
		if err != nil {
			t.Fatalf("AuditUsesFunctions: %v", err)
		}
		if len(results) != 1 {
			t.Fatalf("expected 1 result, got %d", len(results))
		}
		got := results[0]
		if len(got.Missing) != 1 || got.Missing[0] != "random_hex_id_go_core" {
			t.Errorf("Missing = %v, want [random_hex_id_go_core]", got.Missing)
		}
		if len(got.Unused) != 0 {
			t.Errorf("Unused = %v, want []", got.Unused)
		}
	})
}

// TestAuditUsesFunctions_DetectsUnused verifies that a function declared in
// uses_functions but not called in source is reported as unused.
func TestAuditUsesFunctions_DetectsUnused(t *testing.T) {
	t.Run("unused function detected for Go app", func(t *testing.T) {
		root := t.TempDir()
		createTestRegistryDB(t, root, []struct {
			id, lang, dirPath, usesFunctions string
		}{
			{"testapp2_go_tools", "go", "apps/testapp2", `["random_hex_id_go_core"]`},
		})

		appDir := filepath.Join(root, "apps", "testapp2")
		if err := os.MkdirAll(appDir, 0755); err != nil {
			t.Fatal(err)
		}
		goSrc := `package main

import "fmt"

func main() { fmt.Println("hello") }
`
		if err := os.WriteFile(filepath.Join(appDir, "main.go"), []byte(goSrc), 0644); err != nil {
			t.Fatal(err)
		}

		results, err := AuditUsesFunctions(root)
		if err != nil {
			t.Fatalf("AuditUsesFunctions: %v", err)
		}
		if len(results) != 1 {
			t.Fatalf("expected 1 result, got %d", len(results))
		}
		got := results[0]
		if len(got.Unused) != 1 || got.Unused[0] != "random_hex_id_go_core" {
			t.Errorf("Unused = %v, want [random_hex_id_go_core]", got.Unused)
		}
		if len(got.Missing) != 0 {
			t.Errorf("Missing = %v, want []", got.Missing)
		}
	})
}

// TestAuditUsesFunctions_MissingDir verifies that apps whose dir_path does not
// exist on disk get an entry with nil Missing/Unused slices (cannot inspect).
func TestAuditUsesFunctions_MissingDir(t *testing.T) {
	t.Run("missing dir returns entry with nil slices", func(t *testing.T) {
		root := t.TempDir()
		createTestRegistryDB(t, root, []struct {
			id, lang, dirPath, usesFunctions string
		}{
			{"testapp3_go_tools", "go", "apps/testapp3", `[]`},
		})
		// intentionally do NOT create apps/testapp3 on disk

		results, err := AuditUsesFunctions(root)
		if err != nil {
			t.Fatalf("AuditUsesFunctions: %v", err)
		}
		if len(results) != 1 {
			t.Fatalf("expected 1 result, got %d", len(results))
		}
		got := results[0]
		if got.Missing != nil {
			t.Errorf("Missing should be nil for missing dir, got %v", got.Missing)
		}
		if got.Unused != nil {
			t.Errorf("Unused should be nil for missing dir, got %v", got.Unused)
		}
	})
}
