package bindingowner

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"testing"
)

var bindingOwnerAuthorityMutationPattern = regexp.MustCompile(
	`(?i)\b(?:INSERT\s+INTO|UPDATE|DELETE\s+FROM)\s+` +
		"`?_System_PackGeneration(?:ConnectorBindingDefinition(?:Set)?)?`?",
)

func TestPackageContractKeepsBindingOwnerSourceFreeExactAndReadOnly(t *testing.T) {
	entries, err := os.ReadDir(".")
	if err != nil {
		t.Fatal(err)
	}
	productionFiles := 0
	for _, entry := range entries {
		name := entry.Name()
		if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
			continue
		}
		productionFiles++
		raw, err := os.ReadFile(filepath.Clean(name))
		if err != nil {
			t.Fatal(err)
		}
		violations, err := bindingOwnerContractViolations(name, raw)
		if err != nil {
			t.Fatalf("parse %s: %v", name, err)
		}
		for _, violation := range violations {
			t.Errorf("%s: %s", name, violation)
		}
	}
	if productionFiles == 0 {
		t.Fatal("bindingowner package has no production Go source")
	}
}

func TestPackageContractScannerIgnoresCommentsAndRejectsForbiddenShapes(t *testing.T) {
	comments := []byte(`package bindingowner
// ResolveLatest reads _System_InstalledPack and UPDATEs _System_PackGeneration.
func resolveExact() {}
`)
	violations, err := bindingOwnerContractViolations("comments.go", comments)
	if err != nil {
		t.Fatal(err)
	}
	if len(violations) != 0 {
		t.Fatalf("comment-only vocabulary produced violations: %v", violations)
	}

	tests := []struct {
		name   string
		source string
		want   string
	}{
		{
			name:   "authority mutation",
			source: "package bindingowner\nconst query = `UPDATE _System_PackGeneration SET PackName = ?`\n",
			want:   "mutates immutable authority",
		},
		{
			name:   "mutable installation authority",
			source: "package bindingowner\nconst query = `SELECT * FROM _System_InstalledPack`\n",
			want:   "mutable installation authority",
		},
		{
			name:   "latest API",
			source: "package bindingowner\nfunc ResolveLatest() {}\n",
			want:   `exported API "ResolveLatest"`,
		},
		{
			name:   "source files",
			source: "package bindingowner\nimport _ \"io/fs\"\n",
			want:   `imports forbidden source package "io/fs"`,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			violations, err := bindingOwnerContractViolations(test.name+".go", []byte(test.source))
			if err != nil {
				t.Fatal(err)
			}
			if !containsBindingOwnerViolation(violations, test.want) {
				t.Fatalf("violations = %v, want one containing %q", violations, test.want)
			}
		})
	}
}

func bindingOwnerContractViolations(filename string, source []byte) ([]string, error) {
	fileSet := token.NewFileSet()
	// Comments are intentionally omitted: the guard owns executable read paths,
	// not documentation that explains the forbidden compatibility paths.
	file, err := parser.ParseFile(fileSet, filename, source, 0)
	if err != nil {
		return nil, err
	}
	var rendered bytes.Buffer
	if err := format.Node(&rendered, fileSet, file); err != nil {
		return nil, err
	}
	commentFree := rendered.String()

	var violations []string
	if match := bindingOwnerAuthorityMutationPattern.FindString(commentFree); match != "" {
		violations = append(violations, fmt.Sprintf("mutates immutable authority with %q", match))
	}
	if strings.Contains(strings.ToLower(commentFree), "_system_installedpack") {
		violations = append(violations, "references mutable installation authority")
	}
	for _, imported := range file.Imports {
		path, err := strconv.Unquote(imported.Path.Value)
		if err != nil {
			return nil, err
		}
		if path == "os" || path == "io/fs" || path == "path/filepath" || path == "net/http" {
			violations = append(violations, fmt.Sprintf("imports forbidden source package %q", path))
		}
	}
	ast.Inspect(file, func(node ast.Node) bool {
		var name *ast.Ident
		switch declaration := node.(type) {
		case *ast.FuncDecl:
			name = declaration.Name
		case *ast.TypeSpec:
			name = declaration.Name
		}
		if name == nil || !ast.IsExported(name.Name) {
			return true
		}
		lower := strings.ToLower(name.Name)
		for _, forbidden := range []string{
			"current", "latest", "default", "fallback", "list", "write", "repair", "source",
		} {
			if strings.Contains(lower, forbidden) {
				violations = append(violations, fmt.Sprintf(
					"declares exported API %q with forbidden %q semantics",
					name.Name,
					forbidden,
				))
				break
			}
		}
		return true
	})
	return violations, nil
}

func containsBindingOwnerViolation(violations []string, want string) bool {
	for _, violation := range violations {
		if strings.Contains(violation, want) {
			return true
		}
	}
	return false
}
