package exec

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strings"
	"testing"
)

// repoRoot resolves the repository root (the directory containing BOTH the
// `desktop/` and `internal/` top-level dirs — the main server module) from the
// test file's own path, so the drift check is robust to the working directory
// and does not stop at the desktop module's own `internal/` dir.
func repoRoot(t *testing.T) string {
	t.Helper()
	_, file, _, ok := runtime.Caller(0)
	if !ok {
		t.Fatal("cannot resolve test file path")
	}
	dir := filepath.Dir(file)
	for {
		hasInternal := false
		hasDesktop := false
		if info, err := os.Stat(filepath.Join(dir, "internal")); err == nil && info.IsDir() {
			hasInternal = true
		}
		if info, err := os.Stat(filepath.Join(dir, "desktop")); err == nil && info.IsDir() {
			hasDesktop = true
		}
		if hasInternal && hasDesktop {
			return dir
		}
		parent := filepath.Dir(dir)
		if parent == dir {
			t.Fatal("cannot locate repository root")
		}
		dir = parent
	}
}

// serverEmitterFiles enumerates every source file in the main module that can
// emit a Workflow error code. The workflow domain packages (workflowplan,
// workflowbinding, workflowinvocation, workflowrun, teamworkflow) contribute all
// their non-test files; the agent package contributes only its workflow-specific
// files (api_workflow_*.go and workflow_*_transport.go) so non-workflow
// agent-management emitters are excluded. The manage ErrorCode constants, the
// callback validation codes, and the publicerr default are included explicitly.
func serverEmitterFiles(root string) []string {
	var files []string
	for _, dir := range []string{
		"internal/api/workflow/plan",
		"internal/api/workflow/binding",
		"internal/api/workflow/invocation",
		"internal/api/workflow/run",
		"internal/api/teamworkflow",
	} {
		_ = filepath.Walk(filepath.Join(root, dir), func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return nil
			}
			if !info.IsDir() && strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, "_test.go") {
				files = append(files, path)
			}
			return nil
		})
	}
	// Agent workflow-specific emitters only.
	for _, pattern := range []string{"api_workflow_*.go", "workflow_*_transport.go"} {
		matches, _ := filepath.Glob(filepath.Join(root, "internal/api/agent", pattern))
		for _, match := range matches {
			if !strings.HasSuffix(match, "_test.go") {
				files = append(files, match)
			}
		}
	}
	files = append(files,
		filepath.Join(root, "internal/workflow/planmanage/errors.go"),
		filepath.Join(root, "internal/workflow/bindingmanage/errors.go"),
		filepath.Join(root, "internal/workflow/invocationmanage/errors.go"),
		filepath.Join(root, "internal/workflow/runmanage/errors.go"),
		filepath.Join(root, "internal/workflow/orchestrator/callback_validation.go"),
		filepath.Join(root, "internal/workflow/publicerr/publicerr.go"),
	)
	return files
}

// emitterCodePatterns match the exact sites where a Workflow error code is
// emitted: ErrorResponse struct Code fields, publicError(...), the API
// NewHTTPErrorWithCode helper, the manage ErrorCode constants, the callback
// validation constants, the publicerr DefaultCode option, and the Plan
// Agent wake/edit classifier's code argument (classifyPlanWakeEditError).
var emitterCodePatterns = []*regexp.Regexp{
	regexp.MustCompile(`Code:\s*"([a-z_]+)"`),
	regexp.MustCompile(`publicError\(\s*(?:http\.[A-Za-z]+|[A-Za-z_][A-Za-z0-9_]*|[^,]+?)\s*,\s*"([a-z_]+)"`),
	regexp.MustCompile(`NewHTTPErrorWithCode\([^,]+,[^,]+,\s*"([a-z_]+)"\)`),
	regexp.MustCompile(`ErrorCode[A-Za-z_]+(?:\s+ErrorCode)?\s*=\s*"([a-z_]+)"`),
	regexp.MustCompile(`CallbackValidationCode[A-Za-z]+\s*=\s*"([a-z_]+)"`),
	regexp.MustCompile(`workflowRunCallback\w+Code\s*=\s*"([a-z_]+)"`),
	regexp.MustCompile(`DefaultCode:\s*"([a-z_]+)"`),
	regexp.MustCompile(`classifyPlanWakeEditError\(\s*"([a-z_]+)"`),
	// ProtectedNotFoundCode is the canonical not-found code a domain classifier
	// emits for a raw store NotFound/Forbidden selector (for example
	// team_workflow_not_found).
	regexp.MustCompile(`ProtectedNotFoundCode:\s*"([a-z_]+)"`),
}

// dynamicInvalidFieldEmitter matches the server's dynamic `"invalid_"+field`
// code emitter (workflowplan/api_agent_edit.go optionalPositiveInt64). The bare
// `invalid_` prefix is a compile-time artifact, not a code; the concrete
// production value is asserted through dynamicEmitterFamilyValues.
var dynamicInvalidFieldEmitter = regexp.MustCompile(`"invalid_"\s*\+`)

// dynamicEmitterFamilyValues are the concrete codes produced by the server's
// DYNAMIC emitters, which cannot be enumerated as static literals:
//   - "invalid_"+field from the plan agent-edit validation; the only production
//     call passes field "beforeSeq" (workflowplan/api_agent_edit.go optionalPositiveInt64).
//   - authoring_failed, written to the team-workflow relation LastErrorCode via
//     the store params.ErrorCode input (the canonical approval authoring failure).
var dynamicEmitterFamilyValues = map[string]struct{}{
	"invalid_beforeSeq": {},
	"authoring_failed":  {},
}

// TestWorkflowEmitterRegistryDrift is the EXHAUSTIVE emitter-versus-registry
// drift check (not a curated sample). It scans every server emitter file for
// statically-emitted Workflow error codes and asserts:
//
//  1. Every statically-emitted code is present in workflowTrustedServerErrorCodes
//     (an un-reviewed new emitter fails the build).
//  2. Every registry code is either statically emitted OR one of the documented
//     dynamic-emitter family values (so the registry never silently drops a
//     reviewed code the server stopped emitting, and never drifts from the set
//     of real emitters).
func TestWorkflowEmitterRegistryDrift(t *testing.T) {
	root := repoRoot(t)
	emitted := map[string]struct{}{}
	var emitters []string
	for _, file := range serverEmitterFiles(root) {
		content, err := os.ReadFile(file)
		if err != nil {
			continue
		}
		text := string(content)
		matched := false
		for _, pattern := range emitterCodePatterns {
			for _, match := range pattern.FindAllStringSubmatch(text, -1) {
				emitted[match[1]] = struct{}{}
				matched = true
			}
		}
		if matched {
			emitters = append(emitters, filepath.ToSlash(strings.TrimPrefix(file, root+string(filepath.Separator))))
		}
	}
	// publicerr default fallback.
	emitted["workflow_request_failed"] = struct{}{}

	// Direction 1: every statically-emitted code must be trusted. The bare
	// `invalid_` compile-time artifact of the dynamic invalid_+field emitter is
	// excluded (the concrete production value invalid_beforeSeq is asserted
	// through dynamicEmitterFamilyValues below).
	var missing []string
	for code := range emitted {
		if code == "invalid_" {
			continue
		}
		if _, ok := workflowTrustedServerErrorCodes[code]; !ok {
			missing = append(missing, code)
		}
	}
	sort.Strings(missing)
	if len(missing) > 0 {
		t.Fatalf("registry drift: %d statically-emitted Workflow codes are not in the trusted set (add them to workflowTrustedServerErrorCodes after review): %v", len(missing), missing)
	}

	// Direction 2: every trusted code must be either statically emitted or a
	// documented dynamic-emitter family value (no orphaned/reviewed-but-removed
	// codes and no missing emitters).
	var orphaned []string
	for code := range workflowTrustedServerErrorCodes {
		if _, ok := emitted[code]; ok {
			continue
		}
		if _, ok := dynamicEmitterFamilyValues[code]; ok {
			continue
		}
		orphaned = append(orphaned, code)
	}
	sort.Strings(orphaned)
	if len(orphaned) > 0 {
		t.Fatalf("registry drift: %d trusted codes have no static server emitter and are not a documented dynamic family value (either the emitter was removed or the code is stale): %v", len(orphaned), orphaned)
	}

	// The dynamic invalid_+field emitter must have its concrete production value
	// trusted (the only production call passes field "beforeSeq").
	dynamicInvalidEmitterFound := false
	for _, file := range serverEmitterFiles(root) {
		content, err := os.ReadFile(file)
		if err != nil {
			continue
		}
		if dynamicInvalidFieldEmitter.Match(content) {
			dynamicInvalidEmitterFound = true
			break
		}
	}
	if dynamicInvalidEmitterFound {
		if _, ok := workflowTrustedServerErrorCodes["invalid_beforeSeq"]; !ok {
			t.Fatal("the dynamic invalid_+field emitter is present but its concrete production value invalid_beforeSeq is not in the trusted set")
		}
	}

	// A convenience note: the scanned emitter set is the exhaustive workflow
	// error projection; keep this list in sync when a new workflow domain package is
	// added.
	t.Logf("scanned %d workflow emitter files; %d static codes + %d dynamic family values in the registry", len(emitters), len(emitted), len(dynamicEmitterFamilyValues))
}

// TestWorkflowTrustedContractVersionBumpOnRegistryChange guards the version
// contract: any change to the explicit trusted set must bump
// workflowTrustedErrorContractVersion so reviewers see the audit boundary move.
func TestWorkflowTrustedContractVersionBumpOnRegistryChange(t *testing.T) {
	// Version 3 reviewed the explicit step_forbidden projection. Keep this
	// monotonic assertion in sync so future registry edits cannot ship silently.
	if workflowTrustedErrorContractVersion < 3 {
		t.Fatalf("workflowTrustedErrorContractVersion = %d, want >= 3", workflowTrustedErrorContractVersion)
	}
}

var _ = fmt.Sprintf // keep fmt import stable
