package packs

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

// TestStandardActionDefinitionsStayOutOfSeeder guards the Wave-0/W4 relocation
// of the authored *Def shapes and the StandardActionInternalAccessScopeRow
// projection into the contract module's seedrows package. The standard-action
// seeder + its type aliases were themselves relocated from internal/bootstrap
// to internal/controlplane/packs in Wave 5, so this structural guard reads the
// packs sources. Backend runtime reader aliases are owned and tested by the
// backend composition repo, not by this external controlplane module.
func TestStandardActionDefinitionsStayOutOfSeeder(t *testing.T) {
	seederSrc := readPacksSourceForStructure(t, "standard_actions.go")
	typesSrc := readPacksSourceForStructure(t, "standard_actions_types.go")

	relocatedToContract := []string{
		"StandardActionDef",
		"StandardActionSchemaDef",
		"StandardActionDefaultDef",
		"StandardActionGuardDef",
		"StandardActionInternalAccessScopeDef",
		"StandardActionCustomPermissionDef",
	}
	// gofmt aligns the alias block, so the name and "=" are separated by a
	// run of spaces. Collapse whitespace before matching the alias.
	typesNormalized := strings.Join(strings.Fields(typesSrc), " ")
	for _, name := range relocatedToContract {
		if strings.Contains(seederSrc, "type "+name+" struct") {
			t.Fatalf("standard_actions.go still owns definition %q", name)
		}
		alias := name + " = seedrows." + name
		if !strings.Contains(typesNormalized, alias) {
			t.Fatalf("standard_actions_types.go is missing contract alias %q", alias)
		}
	}

	// Authoritative struct bodies now live in the contract module.
	contractSrc := readContractSeedrowsSourceFromPacks(t, "standard_action_defs.go")
	for _, name := range relocatedToContract {
		if !strings.Contains(contractSrc, "type "+name+" struct") {
			t.Fatalf("contract seedrows is missing relocated definition %q", name)
		}
	}

	// StandardActionInternalAccessScopeRow (the runtime projection) lives in the
	// contract seedrows package; its alias now lives in internal/bootstrap with
	// the runtime internal-access-scope reader (Wave 5), not in the relocated
	// packs seeder type file.
	if strings.Contains(seederSrc, "type StandardActionInternalAccessScopeRow struct") {
		t.Fatalf("standard_actions.go still owns StandardActionInternalAccessScopeRow")
	}
	rowSrc := readContractSeedrowsSourceFromPacks(t, "action_rows.go")
	if !strings.Contains(rowSrc, "type StandardActionInternalAccessScopeRow struct") {
		t.Fatalf("contract seedrows/action_rows.go is missing relocated StandardActionInternalAccessScopeRow")
	}

	for _, symbol := range []string{
		"type standardActionInternalAccessOwner struct",
		"StandardActionInternalAccessScopeRow = seedrows.StandardActionInternalAccessScopeRow",
	} {
		if strings.Contains(seederSrc, symbol) {
			t.Fatalf("packs standard_actions.go still owns definition %q", symbol)
		}
	}
}

func readContractSeedrowsSourceFromPacks(t *testing.T, name string) string {
	t.Helper()
	cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", "github.com/pingcap-inc/nexuscrm-contract")
	cmd.Dir = ".."
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go list contract module dir: %v\n%s", err, string(out))
	}
	contractRoot := strings.TrimSpace(string(out))
	if contractRoot == "" {
		t.Fatalf("go list contract module dir returned empty path")
	}
	path := filepath.Join(contractRoot, "pkg", "seedrows", name)
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read %s: %v", path, err)
	}
	return string(data)
}
