package credentialgeneration

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

var (
	mutableCredentialAuthorityPattern = regexp.MustCompile(`(?i)(?:_system_)?(?:namedcredential|connectedprincipal)`)
	generationMutationPattern         = regexp.MustCompile("(?i)\\b(?:UPDATE|DELETE\\s+FROM)\\s+`?_System_ConnectorCredentialGeneration\\b")
	dispatchGateMutationPattern       = regexp.MustCompile("(?i)\\b(INSERT\\s+INTO|UPDATE|DELETE\\s+FROM)\\s+`?_System_ConnectorCredentialGenerationDispatchGate\\b")
)

func TestPackageContractPreservesImmutableExactGenerationAuthority(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 := credentialGenerationContractViolations(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("credentialgeneration package has no production Go source")
	}
}

func TestPackageContractScannerIgnoresComments(t *testing.T) {
	source := []byte(`package credentialgeneration

// _System_NamedCredential and _System_ConnectedPrincipal are mutable.
// UPDATE _System_ConnectorCredentialGeneration is forbidden.
// ResolveLatestCredentialGeneration would be a forbidden exported API.
func resolveExactGeneration() {}
`)
	violations, err := credentialGenerationContractViolations("comments.go", source)
	if err != nil {
		t.Fatal(err)
	}
	if len(violations) != 0 {
		t.Fatalf("comment-only vocabulary produced violations: %v", violations)
	}
}

func TestPackageContractScannerRejectsForbiddenProductionShapes(t *testing.T) {
	tests := []struct {
		name   string
		source string
		want   string
	}{
		{
			name:   "mutable authority",
			source: "package credentialgeneration\nconst table = `_System_NamedCredential`\n",
			want:   "mutable credential authority",
		},
		{
			name:   "generation update",
			source: "package credentialgeneration\nconst query = `UPDATE _System_ConnectorCredentialGeneration SET CreatedDate = NOW()`\n",
			want:   "generation table mutation",
		},
		{
			name:   "generation delete",
			source: "package credentialgeneration\nconst query = `DELETE FROM _System_ConnectorCredentialGeneration WHERE Id = ?`\n",
			want:   "generation table mutation",
		},
		{
			name:   "latest API",
			source: "package credentialgeneration\nfunc ResolveLatestGeneration() {}\n",
			want:   `exported API "ResolveLatestGeneration"`,
		},
		{
			name:   "gate delete even in owner",
			source: "package credentialgeneration\nconst query = `DELETE FROM _System_ConnectorCredentialGenerationDispatchGate WHERE CredentialGenerationId = ?`\n",
			want:   "dispatch-gate deletion",
		},
		{
			name:   "gate mutation outside owner",
			source: "package credentialgeneration\nconst query = `UPDATE _System_ConnectorCredentialGenerationDispatchGate SET FenceToken = 0`\n",
			want:   "outside dispatch_gate_repository.go",
		},
		{
			name:   "generic mode setter",
			source: "package credentialgeneration\nfunc SetMode() {}\n",
			want:   `exported API "SetMode"`,
		},
		{
			name:   "permit surface",
			source: "package credentialgeneration\ntype DispatchPermit struct{}\n",
			want:   `exported API "DispatchPermit"`,
		},
		{
			name:   "plaintext surface",
			source: "package credentialgeneration\nfunc ReadPlaintextSecret() {}\n",
			want:   `exported API "ReadPlaintextSecret"`,
		},
		{
			name:   "http transport dependency",
			source: "package credentialgeneration\nimport _ \"net/http\"\n",
			want:   `imports forbidden transport package "net/http"`,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			violations, err := credentialGenerationContractViolations(test.name+".go", []byte(test.source))
			if err != nil {
				t.Fatal(err)
			}
			if !containsContractViolation(violations, test.want) {
				t.Fatalf("violations = %v, want one containing %q", violations, test.want)
			}
		})
	}
}

func TestDispatchGateDurableStateRemainsMinimalAndPrivate(t *testing.T) {
	type fieldContract struct {
		name   string
		typeOf reflect.Type
	}
	want := []fieldContract{
		{name: "credentialGenerationID", typeOf: reflect.TypeOf("")},
		{name: "mode", typeOf: reflect.TypeOf(DispatchMode(""))},
		{name: "fenceToken", typeOf: reflect.TypeOf(uint64(0))},
	}
	typeOf := reflect.TypeOf(DispatchGate{})
	if typeOf.NumField() != len(want) {
		t.Fatalf("DispatchGate fields = %d, want exactly %d", typeOf.NumField(), len(want))
	}
	for index, expected := range want {
		field := typeOf.Field(index)
		if field.Name != expected.name || field.Type != expected.typeOf || field.PkgPath == "" {
			t.Fatalf("DispatchGate field %d = %s %s exported:%t, want private %s %s",
				index, field.Name, field.Type, field.PkgPath == "", expected.name, expected.typeOf)
		}
	}
}

func credentialGenerationContractViolations(filename string, source []byte) ([]string, error) {
	fileSet := token.NewFileSet()
	// Intentionally omit parser.ParseComments. The package contract applies to
	// executable declarations and literals, not documentation that names the
	// mutable authorities or forbidden compatibility vocabulary.
	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
	}
	commentFreeSource := rendered.String()

	var violations []string
	if match := mutableCredentialAuthorityPattern.FindString(commentFreeSource); match != "" {
		violations = append(violations, fmt.Sprintf("references mutable credential authority %q", match))
	}
	if match := generationMutationPattern.FindString(commentFreeSource); match != "" {
		violations = append(violations, fmt.Sprintf("contains generation table mutation %q", match))
	}
	for _, match := range dispatchGateMutationPattern.FindAllStringSubmatch(commentFreeSource, -1) {
		verb := strings.ToUpper(strings.Join(strings.Fields(match[1]), " "))
		if verb == "DELETE FROM" {
			violations = append(violations, fmt.Sprintf("contains forbidden dispatch-gate deletion %q", match[0]))
		} else if filepath.Base(filename) != "dispatch_gate_repository.go" {
			violations = append(violations, fmt.Sprintf("contains dispatch-gate mutation outside dispatch_gate_repository.go %q", match[0]))
		}
	}
	for _, imported := range file.Imports {
		path, unquoteErr := strconv.Unquote(imported.Path.Value)
		if unquoteErr != nil {
			return nil, unquoteErr
		}
		if path == "net/http" {
			violations = append(violations, `imports forbidden transport package "net/http"`)
		}
	}

	ast.Inspect(file, func(node ast.Node) bool {
		switch declaration := node.(type) {
		case *ast.FuncDecl:
			appendForbiddenExport(&violations, declaration.Name)
		case *ast.TypeSpec:
			appendForbiddenExport(&violations, declaration.Name)
			if ast.IsExported(declaration.Name.Name) {
				ast.Inspect(declaration.Type, func(member ast.Node) bool {
					field, ok := member.(*ast.Field)
					if !ok {
						return true
					}
					for _, name := range field.Names {
						appendForbiddenExport(&violations, name)
					}
					return true
				})
			}
		case *ast.ValueSpec:
			for _, name := range declaration.Names {
				appendForbiddenExport(&violations, name)
			}
		}
		return true
	})

	return violations, nil
}

func appendForbiddenExport(violations *[]string, name *ast.Ident) {
	if name == nil || !ast.IsExported(name.Name) {
		return
	}
	lowerName := strings.ToLower(name.Name)
	if strings.Contains(lowerName, "dispatchpermit") || strings.Contains(lowerName, "plaintext") {
		*violations = append(
			*violations,
			fmt.Sprintf("declares exported API %q outside credential-generation dispatch eligibility", name.Name),
		)
		return
	}
	if lowerName == "transition" || lowerName == "setmode" ||
		(strings.Contains(lowerName, "gate") && containsAnyContractToken(lowerName,
			"set", "update", "ensure", "upsert", "create", "initialize")) {
		*violations = append(
			*violations,
			fmt.Sprintf("declares exported API %q with forbidden generic gate-mutation semantics", name.Name),
		)
		return
	}
	for _, forbidden := range []string{"current", "latest", "default", "fallback"} {
		if strings.Contains(lowerName, forbidden) {
			*violations = append(
				*violations,
				fmt.Sprintf("declares exported API %q with forbidden %q semantics", name.Name, forbidden),
			)
			return
		}
	}
}

func containsAnyContractToken(value string, tokens ...string) bool {
	for _, token := range tokens {
		if strings.Contains(value, token) {
			return true
		}
	}
	return false
}

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