package exec

import (
	"fmt"
	"sort"
	"strings"
)

// The durable audit JSON is a projection of these typed boundary contracts.
// Paths are not schema snippets: they identify a typed request or response
// field and are resolved against the descriptor's parsed contract tree during
// catalog construction.
type auditPathRoot uint8

const (
	auditInputPath auditPathRoot = iota + 1
	auditOutputPath
)

type auditPathSegment struct {
	Name       string
	ArrayItems bool
}

type auditPath struct {
	Root     auditPathRoot
	Segments []auditPathSegment
}

func inputPath(names ...string) auditPath {
	return auditPath{Root: auditInputPath, Segments: pathSegments(names, false)}
}
func outputPath(names ...string) auditPath {
	return auditPath{Root: auditOutputPath, Segments: pathSegments(names, false)}
}
func outputItem(collection, field string) auditPath {
	return auditPath{Root: auditOutputPath, Segments: pathSegments([]string{"data", collection, field}, true)}
}

func pathSegments(names []string, arrayCollection bool) []auditPathSegment {
	segments := make([]auditPathSegment, 0, len(names))
	for i, name := range names {
		segments = append(segments, auditPathSegment{Name: name, ArrayItems: arrayCollection && i == 1})
	}
	return segments
}

type auditSelector struct {
	Label string
	Paths []auditPath
}

func selector(label string, paths ...auditPath) auditSelector {
	return auditSelector{Label: label, Paths: paths}
}

func selectors(items ...auditSelector) []auditSelector { return items }

type auditActionRef struct {
	Domain string
	Name   string
}

func (ref auditActionRef) key() string { return ref.Domain + " " + ref.Name }

func actionRef(domain, name string) auditActionRef { return auditActionRef{Domain: domain, Name: name} }

type auditFollowUpKind uint8

const (
	auditNoFollowUp auditFollowUpKind = iota + 1
	auditReadFollowUp
	auditCursorFollowUp
	auditPageFollowUp
)

type auditSelectorBuilder uint8

const (
	auditBuilderNone auditSelectorBuilder = iota
	auditBuilderByAgentID
	auditBuilderByTeamID
	auditBuilderByChannelID
	auditBuilderByContextID
	auditBuilderByRoleID
	auditBuilderByRequestID
	auditBuilderBySkillID
	auditBuilderByTaskLocator
	auditBuilderByScheduleID
	auditBuilderByCursor
	auditBuilderByPage
)

type auditFollowUp struct {
	Kind          auditFollowUpKind
	Target        auditActionRef
	Builder       auditSelectorBuilder
	SelectorPaths []auditPath
	Documentation string
}

func noFollowUp(documentation string) auditFollowUp {
	return auditFollowUp{Kind: auditNoFollowUp, Documentation: documentation}
}

func readFollowUp(target auditActionRef, builder auditSelectorBuilder, documentation string, paths ...auditPath) auditFollowUp {
	return auditFollowUp{Kind: auditReadFollowUp, Target: target, Builder: builder, SelectorPaths: paths, Documentation: documentation}
}

func cursorFollowUp(target auditActionRef, documentation string) auditFollowUp {
	return auditFollowUp{Kind: auditCursorFollowUp, Target: target, Builder: auditBuilderByCursor, SelectorPaths: []auditPath{inputPath("cursor"), outputPath("data", "nextCursor")}, Documentation: documentation}
}

func pageFollowUp(target auditActionRef, documentation string) auditFollowUp {
	return auditFollowUp{Kind: auditPageFollowUp, Target: target, Builder: auditBuilderByPage, SelectorPaths: []auditPath{inputPath("page"), outputPath("data", "page")}, Documentation: documentation}
}

type auditTransportKind uint8

const (
	auditTransportLocalRead auditTransportKind = iota + 1
	auditTransportRemoteRead
	auditTransportNaturalMutation
	auditTransportRequestReceipt
	auditTransportIdempotencyReceipt
	auditTransportWorkflowReceipt
)

type auditDigestKind uint8

const (
	auditDigestNone auditDigestKind = iota + 1
	auditDigestCanonicalRequest
	auditDigestCanonicalWrite
	auditDigestAuthoritativeResource
)

type auditRetryKind uint8

const (
	auditRetryNone auditRetryKind = iota + 1
	auditRetryReadSafe
	auditRetryOneTransient
	auditRetryReceiptOnly
)

type auditReplayContract struct {
	Policy        builtinMutationReplayPolicy
	Transport     auditTransportKind
	SelectorPaths []auditPath
	Digest        auditDigestKind
	Retry         auditRetryKind
	Documentation string
}

type auditErrorContract struct {
	Policy builtinErrorPolicyIdentity
	Codes  []ErrorCode
	Exits  map[ErrorCode]int
	Route  string
}

type auditHelpContract struct {
	ExampleCount       int
	RenderedActionHelp bool
	ClosedInputSchema  bool
	ClosedOutputSchema bool
}

type auditCompatibilityKind uint8

const (
	auditCompatibilityExisting auditCompatibilityKind = iota + 1
	auditCompatibilityProjectionNarrowing
	auditCompatibilityPermissionMetadata
	auditCompatibilityReplayAdditive
)

type auditCompatibilityContract struct {
	Kind          auditCompatibilityKind
	Documentation string
}

type auditSensitiveField uint8

const (
	auditSensitiveNone auditSensitiveField = iota + 1
	auditSensitiveCredentialValues
	auditSensitiveURLSecrets
	auditSensitiveInternalEndpoint
	auditSensitiveSourceIdentity
	auditSensitiveAllowedPaths
	auditSensitiveAbsolutePath
	auditSensitiveImportedContent
	auditSensitiveWorkflowPayload
	auditSensitiveImageBytes
)

type builtinActionAuditContract struct {
	Allowed            []auditSensitiveField
	AllowedOutputPaths []auditPath
	Redacted           []auditSensitiveField
	Selectors          []auditSelector
	FollowUp           auditFollowUp
	Replay             auditReplayContract
	Errors             auditErrorContract
	Help               auditHelpContract
	Compatibility      auditCompatibilityContract
}

func enrichBuiltinAudit(policy builtinActionPolicy, decision builtinActionAuditDecision) builtinActionAuditDecision {
	// Inventory rows carry risk/result metadata while the action-specific
	// decision table carries replay/exposure identity. Validate the combined
	// typed view; never infer a mutation mode from the action name alone.
	policy.builtinActionAuditDecision = decision
	capability, capabilityOK := builtinCapabilityForAction(builtinActionKey(policy.Domain, policy.Name))
	if capabilityOK {
		// MutationReplay is a durable projection only. The registered
		// invoker's transport is the authority; the reviewed row never drives
		// replay behavior or validation.
		decision.MutationReplay = capability.replayPolicy()
	}
	contract, ok := typedAuditContract(policy, capability)
	if !capabilityOK {
		return decision
	}
	if !ok {
		return decision
	}
	decision.AllowedSensitiveFields = renderSensitiveFields(contract.Allowed)
	decision.RedactedSensitiveFields = renderSensitiveFields(contract.Redacted)
	decision.StableSelectors = renderSelectors(contract.Selectors)
	decision.CanonicalFollowUp = contract.FollowUp.Documentation
	decision.ReplaySelector = contract.Replay.Documentation
	decision.ReplayDigest = renderDigest(contract.Replay.Digest)
	decision.Retryability = renderRetry(contract.Replay.Retry)
	decision.ExpectedErrorClass = renderError(contract.Errors)
	decision.HelpExampleStatus = renderHelp(contract.Help)
	decision.CompatibilityDecision = contract.Compatibility.Documentation
	return decision
}

func renderSensitiveFields(fields []auditSensitiveField) []string {
	out := make([]string, 0, len(fields))
	for _, field := range fields {
		switch field {
		case auditSensitiveNone:
			out = append(out, "none")
		case auditSensitiveCredentialValues:
			out = append(out, "authorization and credential values")
		case auditSensitiveURLSecrets:
			out = append(out, "URL userinfo/query/fragment credentials")
		case auditSensitiveInternalEndpoint:
			out = append(out, "internal endpoint and network location")
		case auditSensitiveSourceIdentity:
			out = append(out, "source identifier and owner identity")
		case auditSensitiveAllowedPaths:
			out = append(out, "raw policy allowed paths")
		case auditSensitiveAbsolutePath:
			out = append(out, "absolute local filesystem path")
		case auditSensitiveImportedContent:
			out = append(out, "imported file/repository contents")
		case auditSensitiveWorkflowPayload:
			out = append(out, "raw workflow payload outside the typed operation")
		case auditSensitiveImageBytes:
			out = append(out, "image bytes")
		default:
			out = append(out, fmt.Sprintf("unknown-sensitive-field-%d", field))
		}
	}
	return out
}

func renderSelectors(selectors []auditSelector) []string {
	out := make([]string, 0, len(selectors))
	for _, item := range selectors {
		out = append(out, item.Label)
	}
	return out
}

func renderDigest(kind auditDigestKind) string {
	switch kind {
	case auditDigestNone:
		return "none: no mutation digest"
	case auditDigestCanonicalRequest:
		return "canonical request payload digest including requestId and action; conflicting reuse is a 409"
	case auditDigestCanonicalWrite:
		return "canonical write payload digest including the idempotency key; conflicting reuse is a 409"
	case auditDigestAuthoritativeResource:
		return "not persisted by this API; authoritative resource inspection is required"
	default:
		return "unknown digest contract"
	}
}

func renderRetry(kind auditRetryKind) string {
	switch kind {
	case auditRetryNone:
		return "no automatic retry"
	case auditRetryReadSafe:
		return "one bounded transient retry through the read transport"
	case auditRetryOneTransient:
		return "one bounded transient retry through the receipt boundary"
	case auditRetryReceiptOnly:
		return "receipt replay only; retry only when the durable selector is present"
	default:
		return "unknown retry contract"
	}
}

func renderError(contract auditErrorContract) string {
	codes := make([]string, 0, len(contract.Codes))
	for _, code := range contract.Codes {
		codes = append(codes, fmt.Sprintf("%s=%d", code, contract.Exits[code]))
	}
	sort.Strings(codes)
	return strings.Join(codes, ";")
}

func renderHelp(contract auditHelpContract) string {
	return fmt.Sprintf("registered action help; examples=%d; rendered=%t; closed input/output schemas=%t/%t", contract.ExampleCount, contract.RenderedActionHelp, contract.ClosedInputSchema, contract.ClosedOutputSchema)
}

func typedAuditContract(policy builtinActionPolicy, capabilities ...builtinInvokerCapability) (builtinActionAuditContract, bool) {
	key := builtinActionKey(policy.Domain, policy.Name)
	capability, ok := builtinCapabilityForAction(key)
	if len(capabilities) == 1 {
		capability, ok = capabilities[0], true
	}
	if !ok || capability.validate(key) != nil {
		return builtinActionAuditContract{}, false
	}
	selectors := typedAuditSelectors(key)
	followUp, followOK := typedAuditFollowUp(key)
	replay, replayOK := typedAuditReplay(key, capability)
	if len(selectors) == 0 || !followOK || !replayOK {
		return builtinActionAuditContract{}, false
	}
	allowed := []auditSensitiveField{auditSensitiveNone}
	redacted := []auditSensitiveField{auditSensitiveCredentialValues, auditSensitiveURLSecrets, auditSensitiveInternalEndpoint}
	if policy.Domain == "agent" && (policy.Name == "list" || policy.Name == "get") {
		allowed = []auditSensitiveField{auditSensitiveNone}
		redacted = []auditSensitiveField{auditSensitiveCredentialValues, auditSensitiveURLSecrets, auditSensitiveInternalEndpoint, auditSensitiveSourceIdentity, auditSensitiveAllowedPaths}
	}
	if policy.Domain == "profile" {
		redacted = []auditSensitiveField{auditSensitiveImageBytes, auditSensitiveAbsolutePath}
	}
	if policy.Domain == "skill" && policy.Name == "import" {
		redacted = []auditSensitiveField{auditSensitiveImportedContent, auditSensitiveURLSecrets}
	}
	if policy.Domain == "workflow" {
		redacted = []auditSensitiveField{auditSensitiveCredentialValues, auditSensitiveWorkflowPayload}
	}
	return builtinActionAuditContract{
		Allowed: allowed, AllowedOutputPaths: typedAllowedOutputPaths(key, selectors), Redacted: redacted, Selectors: selectors, FollowUp: followUp,
		Replay:        replay,
		Errors:        auditErrorsFor(capability),
		Help:          auditHelpContract{ExampleCount: 1, RenderedActionHelp: true, ClosedInputSchema: true, ClosedOutputSchema: true},
		Compatibility: auditCompatibilityFor(policy, key, capability),
	}, true
}

func auditErrorsFor(capability builtinInvokerCapability) auditErrorContract {
	codes := builtinErrorPolicyCodes(capability.ErrorPolicy)
	exits := make(map[ErrorCode]int, len(codes))
	for _, code := range codes {
		exits[code] = ErrorExitCode(code)
	}
	return auditErrorContract{Policy: capability.ErrorPolicy, Codes: codes, Exits: exits, Route: capability.Route}
}

func auditCompatibilityFor(policy builtinActionPolicy, key string, capability builtinInvokerCapability) auditCompatibilityContract {
	if key == "agent list" || key == "agent get" {
		return auditCompatibilityContract{Kind: auditCompatibilityProjectionNarrowing, Documentation: "Exec projection intentionally omits credential values, URL credentials, endpoints, source IDs, and raw allowed paths; reviewed safe metadata remains available."}
	}
	if policy.Risk == builtinRiskExistingGate {
		return auditCompatibilityContract{Kind: auditCompatibilityPermissionMetadata, Documentation: "Existing approval gate is now advertised in the typed permission/audit metadata; request and response JSON names are unchanged."}
	}
	if capability.Transport == builtinTransportNaturalMutation {
		return auditCompatibilityContract{Kind: auditCompatibilityExisting, Documentation: "Existing natural-key mutation remains non-retried because the current upstream contract has no durable receipt."}
	}
	return auditCompatibilityContract{Kind: auditCompatibilityReplayAdditive, Documentation: "Existing request and response JSON remain compatible; durable replay is additive at the supported receipt boundary."}
}

func typedAuditReplay(key string, capability builtinInvokerCapability) (auditReplayContract, bool) {
	if capability.Transport == builtinTransportLocalRead || capability.Transport == builtinTransportRemoteRead {
		return auditReplayContract{Policy: capability.replayPolicy(), Transport: capability.auditTransport(), SelectorPaths: nil, Digest: capability.auditDigest(), Retry: capability.auditRetry(), Documentation: "none: read-only action"}, true
	}
	documentation := "stable resource selector only; no durable replay selector"
	switch capability.Selector {
	case builtinSelectorRequestID:
		documentation = "requestId receipt scoped by workspace, routed Agent, and action"
	case builtinSelectorIdempotency:
		documentation = "idempotencyKey receipt scoped by workspace, routed Agent, and resource"
	case builtinSelectorWorkflow:
		documentation = "Workflow operation receipt scoped by workspace, routed Agent, operation, and command identity"
	case builtinSelectorNatural:
	default:
		return auditReplayContract{}, false
	}
	return auditReplayContract{
		Policy: capability.replayPolicy(), Transport: capability.auditTransport(), SelectorPaths: append([]auditPath(nil), capability.SelectorPaths...),
		Digest: capability.auditDigest(), Retry: capability.auditRetry(), Documentation: documentation,
	}, true
}
