package contract_test

import (
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
)

// TestNeutralBoundaryForbiddenImports keeps the seam-6 neutral shared packages
// transport-free. Shared contracts may describe domain outcomes, but gin,
// net/http status handling, backend services, and v63 apierr mapping belong in
// dialect/backend binders.
func TestNeutralBoundaryForbiddenImports(t *testing.T) {
	sealedDirs := []string{".", "../errors", "../queryspec", "../recordtypes"}
	forbiddenImports := []string{
		"github.com/nexuscrm/backend",
		"gin-gonic",
		"net/http",
		"pkg/apierr",
	}

	fset := token.NewFileSet()
	for _, dir := range sealedDirs {
		entries, err := os.ReadDir(dir)
		if err != nil {
			t.Fatalf("read sealed dir %q: %v", dir, err)
		}
		for _, entry := range entries {
			name := entry.Name()
			if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
				continue
			}
			path := filepath.Join(dir, name)
			file, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly)
			if err != nil {
				t.Fatalf("parse %q: %v", path, err)
			}
			for _, imp := range file.Imports {
				importPath := strings.Trim(imp.Path.Value, `"`)
				for _, forbidden := range forbiddenImports {
					if strings.Contains(importPath, forbidden) {
						t.Errorf("%s imports forbidden %q across the neutral boundary: %q", path, forbidden, importPath)
					}
				}
			}
		}
	}
}

func TestNeutralBoundaryTransitiveDependenciesStayTransportFree(t *testing.T) {
	sealedPackages := []string{
		"github.com/nexuscrm/shared/pkg/contract",
		"github.com/nexuscrm/shared/pkg/errors",
		"github.com/nexuscrm/shared/pkg/queryspec",
		"github.com/nexuscrm/shared/pkg/recordtypes",
	}
	forbiddenDeps := []string{
		"github.com/nexuscrm/backend",
		"github.com/gin-gonic/gin",
		"github.com/nexuscrm/kernel/pkg/apierr",
		"net/http",
	}

	args := append([]string{"list", "-deps"}, sealedPackages...)
	cmd := exec.Command("go", args...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go list -deps neutral shared packages: %v\n%s", err, out)
	}
	for _, dep := range strings.Fields(string(out)) {
		for _, forbidden := range forbiddenDeps {
			if dep == forbidden || strings.HasPrefix(dep, forbidden+"/") {
				t.Errorf("neutral shared packages transitively depend on forbidden package %q", dep)
			}
		}
	}
}

func TestContractPackageDoesNotExposeRawJSONWire(t *testing.T) {
	files := parseContractFiles(t)
	for path, file := range files {
		ast.Inspect(file, func(node ast.Node) bool {
			if ident, ok := node.(*ast.Ident); ok && ident.Name == "RawMessage" {
				t.Errorf("%s: shared contract references RawMessage; shared contract must stay a neutral seam and cannot carry preassembled L1 wire JSON", path)
				return false
			}
			if selector, ok := node.(*ast.SelectorExpr); ok && selector.Sel.Name == "RawMessage" {
				t.Errorf("%s: shared contract references json.RawMessage; shared contract must stay a neutral seam and cannot carry preassembled L1 wire JSON", path)
				return false
			}
			return true
		})
	}
}

func TestContractPackageDoesNotExposeProductWireAdapters(t *testing.T) {
	files := parseContractFiles(t)
	for path, file := range files {
		ast.Inspect(file, func(node ast.Node) bool {
			spec, ok := node.(*ast.TypeSpec)
			if !ok {
				return true
			}
			name := spec.Name.Name
			if strings.HasSuffix(name, "WireAdapter") || strings.HasSuffix(name, "RequestAdapter") {
				t.Errorf("%s: %s exposes product wire/request adapter shape through shared contract; L1 dialects own URL/envelope/request/response grammar", path, name)
			}
			return true
		})
	}
}

func TestContractDepsFieldsAreExplicitlyAudited(t *testing.T) {
	got := contractDepsFieldNames(t)
	want := contractDepsLedgerFieldNames(t)
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("contract.Deps fields = %v, want ledger %v; every shared plane addition or deletion must be a deliberate neutral-seam audit, not incidental growth", got, want)
	}
}

func parseContractFiles(t *testing.T) map[string]*ast.File {
	t.Helper()

	entries, err := os.ReadDir(".")
	if err != nil {
		t.Fatalf("read contract package: %v", err)
	}
	fset := token.NewFileSet()
	files := make(map[string]*ast.File)
	for _, entry := range entries {
		name := entry.Name()
		if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
			continue
		}
		file, err := parser.ParseFile(fset, name, nil, 0)
		if err != nil {
			t.Fatalf("parse %s: %v", name, err)
		}
		files[name] = file
	}
	return files
}

func contractDepsFieldNames(t *testing.T) []string {
	t.Helper()

	files := parseContractFiles(t)
	for _, file := range files {
		for _, decl := range file.Decls {
			gen, ok := decl.(*ast.GenDecl)
			if !ok {
				continue
			}
			for _, rawSpec := range gen.Specs {
				spec, ok := rawSpec.(*ast.TypeSpec)
				if !ok || spec.Name.Name != "Deps" {
					continue
				}
				st, ok := spec.Type.(*ast.StructType)
				if !ok {
					t.Fatalf("contract.Deps is %T, want struct", spec.Type)
				}
				fields := make([]string, 0, len(st.Fields.List))
				for _, field := range st.Fields.List {
					for _, name := range field.Names {
						fields = append(fields, name.Name)
					}
				}
				return fields
			}
		}
	}
	t.Fatal("contract.Deps not found")
	return nil
}

func contractDepsLedgerFieldNames(t *testing.T) []string {
	t.Helper()

	path := filepath.Join("..", "..", "..", "docs", "design", "shared-contract-plane-ledger.md")
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read contract deps ledger %s: %v", path, err)
	}
	allowedVerdicts := map[string]bool{
		"keep":            true,
		"trim":            true,
		"split-or-demote": true,
	}
	seen := map[string]bool{}
	fields := []string{}
	for _, line := range strings.Split(string(data), "\n") {
		line = strings.TrimSpace(line)
		if !strings.HasPrefix(line, "| `") {
			continue
		}
		cells := strings.Split(line, "|")
		if len(cells) < 5 {
			t.Fatalf("%s: malformed ledger row %q", path, line)
		}
		field := strings.TrimSpace(cells[1])
		field = strings.TrimPrefix(strings.TrimSuffix(field, "`"), "`")
		verdict := strings.TrimSpace(cells[2])
		if field == "" {
			t.Fatalf("%s: empty ledger field in row %q", path, line)
		}
		if !allowedVerdicts[verdict] {
			t.Fatalf("%s: ledger field %s has verdict %q, want one of keep/trim/split-or-demote", path, field, verdict)
		}
		if seen[field] {
			t.Fatalf("%s: duplicate contract.Deps ledger row for %s", path, field)
		}
		seen[field] = true
		fields = append(fields, field)
	}
	if len(fields) == 0 {
		t.Fatalf("%s: no contract.Deps ledger rows found", path)
	}
	return fields
}
