package boundary_test

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

var l0ConnectorIngressProductionFiles = []string{
	"backend/internal/integrationservice/process.go",
	"backend/internal/host/adapters/apihost/middleware/csrf.go",
}

var l0ConnectorIngressExternalProductionDirs = []string{
	"features/connector/inbound",
	"features/connector/inboundrouting",
	"interfaces/rest/connector_webhooks",
	"application/connectors",
}

func TestConnectorIngressControlHasPinnedRuntimeConsumer(t *testing.T) {
	root := repoRoot(t)
	connectorsRoot := goModuleDir(t, root, "github.com/pingcap-inc/nexuscrm-connectors")

	gatePath := filepath.Join(connectorsRoot, "features/connector/admission/gate.go")
	gateSource := readFile(t, gatePath)
	for _, want := range []string{
		"FROM _System_ConnectorIngressControl",
		"func NewSQLGate(db *sql.DB) *SQLGate",
		"func isMissingIngressControlTable(err error) bool",
	} {
		if !strings.Contains(gateSource, want) {
			t.Fatalf("%s must contain %q so the admitted schema has a pinned runtime reader", displayPath(root, gatePath), want)
		}
	}

	dispatcherPath := filepath.Join(connectorsRoot, "interfaces/rest/connector_webhooks/dispatcher.go")
	dispatcherSource := readFile(t, dispatcherPath)
	for _, want := range []string{
		`connector_admission "github.com/pingcap-inc/nexuscrm-connectors/features/connector/admission"`,
		"admissionGate    connector_admission.Gate",
		"admissionGate:    connector_admission.NewSQLGate(db)",
		`d.checkAdmission(ctx, c, namespace, secretName, packInstallID, "webhook:"+secretName, rawBody)`,
		"func (d *Dispatcher) checkAdmission(ctx context.Context, c *gin.Context, namespace, secretName, packInstallID, channel string, rawBody []byte) bool",
	} {
		if !containsIgnoringWhitespace(dispatcherSource, want) {
			t.Fatalf("%s must contain %q so connector webhook ingress uses the admitted control table", displayPath(root, dispatcherPath), want)
		}
	}
}

func containsIgnoringWhitespace(source, want string) bool {
	return strings.Contains(collapseWhitespace(source), collapseWhitespace(want))
}

func collapseWhitespace(s string) string {
	return strings.Join(strings.Fields(s), " ")
}

func TestL0ConnectorIngressStaysProviderNeutral(t *testing.T) {
	root := repoRoot(t)
	var files []string
	for _, file := range l0ConnectorIngressProductionFiles {
		files = append(files, filepath.Join(root, file))
	}
	connectorsRoot := goModuleDir(t, root, "github.com/pingcap-inc/nexuscrm-connectors")
	for _, dir := range l0ConnectorIngressExternalProductionDirs {
		files = append(files, productionGoFiles(t, filepath.Join(connectorsRoot, dir))...)
	}

	for _, file := range files {
		rel := displayPath(root, file)
		body := readFile(t, file)
		lowered := strings.ToLower(body)

		if strings.Contains(lowered, "hubspot") {
			t.Fatalf("%s contains provider-specific HubSpot text; L0 connector ingress/runtime code must stay provider-neutral", rel)
		}
		if strings.Contains(lowered, "/services/integrations/hubspot/webhook") {
			t.Fatalf("%s contains legacy HubSpot webhook alias; L0 must expose generic /webhooks/:namespace/*path only", rel)
		}
	}
}

func goModuleDir(t *testing.T, root, module string) string {
	t.Helper()
	cmd := exec.Command("go", "list", "-m", "-f", "{{.Dir}}", module)
	cmd.Dir = filepath.Join(root, "backend")
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go list module dir %s: %v\n%s", module, err, string(out))
	}
	dir := strings.TrimSpace(string(out))
	if dir == "" {
		t.Fatalf("go list module dir %s returned empty path", module)
	}
	return dir
}

func TestCSRFExemptsGenericWebhookIngressOnly(t *testing.T) {
	root := repoRoot(t)
	csrfPath := filepath.Join(root, "backend/internal/host/adapters/apihost/middleware/csrf.go")
	body := readFile(t, csrfPath)
	lowered := strings.ToLower(body)

	if !strings.Contains(body, `"/webhooks/"`) {
		t.Fatalf("csrf.go must keep the generic /webhooks/ ingress exemption for connector webhooks")
	}
	if strings.Contains(lowered, "/services/integrations/hubspot/webhook") {
		t.Fatalf("csrf.go must not exempt a HubSpot-specific webhook path")
	}
	if strings.Contains(lowered, "hubspot") {
		t.Fatalf("csrf.go must not contain provider-specific HubSpot text")
	}
}

func productionGoFiles(t *testing.T, dir string) []string {
	t.Helper()
	entries, err := os.ReadDir(dir)
	if err != nil {
		t.Fatalf("read dir %s: %v", dir, err)
	}
	var files []string
	for _, entry := range entries {
		if entry.IsDir() {
			continue
		}
		name := entry.Name()
		if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
			continue
		}
		files = append(files, filepath.Join(dir, name))
	}
	return files
}

func repoRoot(t *testing.T) string {
	t.Helper()
	_, file, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("runtime.Caller failed")
	}
	dir := filepath.Dir(file)
	for {
		if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
			return dir
		}
		next := filepath.Dir(dir)
		if next == dir {
			t.Fatalf("could not find repo root from %s", file)
		}
		dir = next
	}
}

func readFile(t *testing.T, path string) string {
	t.Helper()
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read %s: %v", path, err)
	}
	return string(data)
}

func displayPath(root, path string) string {
	rel, err := filepath.Rel(root, path)
	if err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
		return filepath.ToSlash(rel)
	}
	return filepath.ToSlash(path)
}

func toRepoRelative(t *testing.T, root, path string) string {
	t.Helper()
	rel, err := filepath.Rel(root, path)
	if err != nil {
		t.Fatalf("rel %s from %s: %v", path, root, err)
	}
	return filepath.ToSlash(rel)
}
