package exec

import (
	"encoding/json"
	"fmt"
	"strings"
)

func validateBuiltinActionConcreteAudit(policy builtinActionPolicy, descriptor actionDescriptor, descriptorCatalog *Catalog, capabilities ...builtinInvokerCapability) error {
	contract, ok := typedAuditContract(policy, capabilities...)
	if !ok {
		return fmt.Errorf("exec: built-in action %s lacks a typed action-specific audit contract", descriptor.identity())
	}
	return validateBuiltinActionContract(policy, descriptor, descriptorCatalog, contract)
}

func validateBuiltinActionContract(policy builtinActionPolicy, descriptor actionDescriptor, descriptorCatalog *Catalog, contract builtinActionAuditContract) error {
	decision := policy.builtinActionAuditDecision
	if err := validateTypedSensitiveFields(descriptor, contract); err != nil {
		return err
	}
	if err := validateTypedSelectors(descriptor, contract.Selectors); err != nil {
		return err
	}
	if err := validateTypedFollowUp(descriptor, descriptorCatalog, contract.FollowUp); err != nil {
		return err
	}
	if err := validateTypedReplay(policy, descriptor, contract.Replay); err != nil {
		return err
	}
	if err := validateTypedErrors(descriptor, contract.Errors); err != nil {
		return err
	}
	if err := validateTypedHelp(descriptor, descriptorCatalog, contract.Help); err != nil {
		return err
	}
	if err := validateTypedCompatibility(policy, descriptor, contract.Compatibility); err != nil {
		return err
	}
	if strings.Join(decision.AllowedSensitiveFields, "\x00") != strings.Join(renderSensitiveFields(contract.Allowed), "\x00") ||
		strings.Join(decision.RedactedSensitiveFields, "\x00") != strings.Join(renderSensitiveFields(contract.Redacted), "\x00") ||
		strings.Join(decision.StableSelectors, "\x00") != strings.Join(renderSelectors(contract.Selectors), "\x00") ||
		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 fmt.Errorf("exec: built-in action %s durable audit projection disagrees with its typed contract", descriptor.identity())
	}
	return nil
}

func validateTypedSensitiveFields(descriptor actionDescriptor, contract builtinActionAuditContract) error {
	if len(contract.Allowed) == 0 || len(contract.Redacted) == 0 || len(contract.AllowedOutputPaths) == 0 {
		return fmt.Errorf("exec: typed audit contract has empty sensitive-field decision")
	}
	for _, path := range contract.AllowedOutputPaths {
		if path.Root != auditOutputPath {
			return fmt.Errorf("exec: built-in action %s allowed projection path is not an output path", descriptor.identity())
		}
		if err := validateAuditPath(descriptor, path); err != nil {
			return fmt.Errorf("exec: built-in action %s allowed projection path: %w", descriptor.identity(), err)
		}
	}
	for _, field := range append(append([]auditSensitiveField{}, contract.Allowed...), contract.Redacted...) {
		if field < auditSensitiveNone || field > auditSensitiveImageBytes {
			return fmt.Errorf("exec: typed audit contract has unknown sensitive field %d", field)
		}
	}
	if descriptor.Domain == "agent" {
		for _, forbidden := range []string{"headers", "baseUrl", "endpoint", "allowedPaths", "sourceId"} {
			if schemaContainsProperty(descriptor.OutputSchema, forbidden) {
				return fmt.Errorf("exec: built-in action %s public projection exposes forbidden sensitive field %q", descriptor.identity(), forbidden)
			}
		}
	}
	return nil
}

func schemaContainsProperty(raw json.RawMessage, wanted string) bool {
	var schema struct {
		Properties map[string]json.RawMessage `json:"properties"`
		Items      json.RawMessage            `json:"items"`
		OneOf      []json.RawMessage          `json:"oneOf"`
		AnyOf      []json.RawMessage          `json:"anyOf"`
	}
	if json.Unmarshal(raw, &schema) != nil {
		return false
	}
	if _, ok := schema.Properties[wanted]; ok {
		return true
	}
	for _, child := range schema.Properties {
		if schemaContainsProperty(child, wanted) {
			return true
		}
	}
	if len(schema.Items) > 0 && schemaContainsProperty(schema.Items, wanted) {
		return true
	}
	for _, branch := range append(schema.OneOf, schema.AnyOf...) {
		if schemaContainsProperty(branch, wanted) {
			return true
		}
	}
	return false
}

func validateTypedSelectors(descriptor actionDescriptor, selectors []auditSelector) error {
	if len(selectors) == 0 {
		return fmt.Errorf("exec: built-in action %s has no typed stable selectors", descriptor.identity())
	}
	seen := make(map[string]struct{}, len(selectors))
	for _, item := range selectors {
		label := strings.TrimSpace(item.Label)
		if label == "" {
			return fmt.Errorf("exec: built-in action %s has an empty typed stable selector", descriptor.identity())
		}
		if _, duplicate := seen[label]; duplicate {
			return fmt.Errorf("exec: built-in action %s repeats typed stable selector %q", descriptor.identity(), label)
		}
		seen[label] = struct{}{}
		if len(item.Paths) == 0 {
			return fmt.Errorf("exec: built-in action %s selector %q has no typed path", descriptor.identity(), label)
		}
		for _, path := range item.Paths {
			if err := validateAuditPath(descriptor, path); err != nil {
				return fmt.Errorf("exec: built-in action %s selector %q: %w", descriptor.identity(), label, err)
			}
		}
	}
	return nil
}

func validateTypedFollowUp(descriptor actionDescriptor, descriptorCatalog *Catalog, followUp auditFollowUp) error {
	if strings.TrimSpace(followUp.Documentation) == "" {
		return fmt.Errorf("exec: built-in action %s has no canonical follow-up decision", descriptor.identity())
	}
	if followUp.Kind == auditNoFollowUp {
		if followUp.Target != (auditActionRef{}) || followUp.Builder != auditBuilderNone || len(followUp.SelectorPaths) != 0 {
			return fmt.Errorf("exec: built-in action %s no-follow-up contract carries a selector target", descriptor.identity())
		}
		return nil
	}
	if followUp.Target.Domain == "" || followUp.Target.Name == "" || followUp.Builder == auditBuilderNone {
		return fmt.Errorf("exec: built-in action %s canonical follow-up lacks target or selector builder", descriptor.identity())
	}
	target, ok := descriptorCatalog.lookup(followUp.Target.Domain, followUp.Target.Name)
	if !ok || target.Permission != permissionReadOnly {
		return fmt.Errorf("exec: built-in action %s follow-up %s is not a registered read descriptor", descriptor.identity(), followUp.Target.key())
	}
	for _, path := range followUp.SelectorPaths {
		if err := validateAuditPath(target, path); err != nil {
			return fmt.Errorf("exec: built-in action %s follow-up %s selector: %w", descriptor.identity(), followUp.Target.key(), err)
		}
	}
	if !followUpBuilderMatches(followUp.Builder, target) {
		return fmt.Errorf("exec: built-in action %s follow-up %s lacks its required selector builder", descriptor.identity(), followUp.Target.key())
	}
	return nil
}

func followUpBuilderMatches(builder auditSelectorBuilder, target actionDescriptor) bool {
	key := target.identity()
	switch builder {
	case auditBuilderByCursor:
		return key == "workspace list" || key == "project list" || key == "team list" || key == "channel list" || key == "review list" || key == "role list" || key == "task list" || key == "scheduled-message list" || key == "scheduled-message runs" || key == "agent list"
	case auditBuilderByPage:
		return key == "skill list"
	case auditBuilderByAgentID:
		return key == "agent get"
	case auditBuilderByTeamID:
		return key == "team get"
	case auditBuilderByChannelID:
		return key == "channel get"
	case auditBuilderByContextID:
		return key == "review list"
	case auditBuilderByRoleID:
		return key == "role get"
	case auditBuilderByRequestID:
		return key == "role request-get"
	case auditBuilderBySkillID:
		return key == "skill get"
	case auditBuilderByTaskLocator:
		return key == "task get"
	case auditBuilderByScheduleID:
		return key == "scheduled-message get"
	default:
		return false
	}
}

func validateTypedReplay(policy builtinActionPolicy, descriptor actionDescriptor, replay auditReplayContract) error {
	capability, ok := builtinCapabilityForAction(descriptor.identity())
	if !ok || capability.validate(descriptor.identity()) != nil {
		return fmt.Errorf("exec: built-in action %s lacks a valid registered transport capability", descriptor.identity())
	}
	// Diagnose malformed typed fields before comparing the complete projection so
	// each replay boundary fails closed for the reason that was corrupted.
	if (capability.Transport == builtinTransportRequestReceipt ||
		capability.Transport == builtinTransportIdempotency ||
		capability.Transport == builtinTransportWorkflow) && len(replay.SelectorPaths) == 0 {
		return fmt.Errorf("exec: built-in action %s receipt transport lacks typed selector paths", descriptor.identity())
	}
	if replay.Policy != capability.replayPolicy() {
		return fmt.Errorf("exec: built-in action %s typed replay policy disagrees with registered transport capability", descriptor.identity())
	}
	if replay.Transport != capability.auditTransport() || replay.Digest != capability.auditDigest() || replay.Retry != capability.auditRetry() || !equalAuditPaths(replay.SelectorPaths, capability.SelectorPaths) {
		return fmt.Errorf("exec: built-in action %s typed replay contract disagrees with registered transport capability", descriptor.identity())
	}
	if policy.Risk == builtinRiskReadOnly {
		if replay.Transport != auditTransportLocalRead && replay.Transport != auditTransportRemoteRead || replay.Digest != auditDigestNone || (replay.Retry != auditRetryNone && replay.Retry != auditRetryReadSafe) {
			return fmt.Errorf("exec: built-in action %s read transport has mutation replay fields", descriptor.identity())
		}
		return nil
	}
	if replay.Transport == auditTransportNaturalMutation {
		if capability.replayPolicy() != builtinReplayNaturalKey || len(replay.SelectorPaths) != 0 || replay.Digest != auditDigestAuthoritativeResource || replay.Retry != auditRetryNone {
			return fmt.Errorf("exec: built-in action %s natural-key transport is not fail-closed", descriptor.identity())
		}
		return nil
	}
	if capability.Transport == builtinTransportNaturalMutation {
		return fmt.Errorf("exec: built-in action %s natural-key action has receipt transport", descriptor.identity())
	}
	if len(replay.SelectorPaths) == 0 {
		return fmt.Errorf("exec: built-in action %s receipt transport lacks typed selector paths", descriptor.identity())
	}
	for _, path := range replay.SelectorPaths {
		if path.Root != auditInputPath {
			return fmt.Errorf("exec: built-in action %s replay selector must be an input path", descriptor.identity())
		}
		if err := validateAuditPath(descriptor, path); err != nil {
			return fmt.Errorf("exec: built-in action %s replay selector: %w", descriptor.identity(), err)
		}
	}
	spec, ok := mutationReceiptSelectorSpecFor(policy.Domain, policy.Name)
	if policy.Domain != "workflow" && !ok {
		return fmt.Errorf("exec: built-in action %s replay transport lacks a durable receipt selector spec", descriptor.identity())
	}
	if policy.Domain != "workflow" {
		properties, err := contractObjectProperties(descriptor.InputSchema)
		if err != nil {
			return fmt.Errorf("exec: built-in action %s replay input contract: %w", descriptor.identity(), err)
		}
		for _, field := range spec.requiredInputScalars {
			if !hasContractProperty(properties, field) {
				return fmt.Errorf("exec: built-in action %s receipt selector requires input field %q", descriptor.identity(), field)
			}
		}
		if spec.requireOutputSelector && len(spec.outputScalars)+len(spec.outputBatches) == 0 {
			return fmt.Errorf("exec: built-in action %s receipt selector lacks typed output identity", descriptor.identity())
		}
	}
	return nil
}

func validateTypedErrors(descriptor actionDescriptor, contract auditErrorContract) error {
	if !contract.Policy.valid() || len(contract.Codes) == 0 || len(contract.Codes) != len(contract.Exits) {
		return fmt.Errorf("exec: built-in action %s error contract is incomplete", descriptor.identity())
	}
	capability, ok := builtinCapabilityForAction(descriptor.identity())
	if !ok || capability.validate(descriptor.identity()) != nil {
		return fmt.Errorf("exec: built-in action %s error contract has no registered invoker capability", descriptor.identity())
	}
	if contract.Route != capability.Route {
		return fmt.Errorf("exec: built-in action %s error contract route disagrees with its registered route", descriptor.identity())
	}
	if contract.Policy != capability.ErrorPolicy {
		return fmt.Errorf("exec: built-in action %s error policy identity disagrees with its registered invoker", descriptor.identity())
	}
	if !equalErrorCodes(contract.Codes, builtinErrorPolicyCodes(capability.ErrorPolicy)) {
		return fmt.Errorf("exec: built-in action %s error classes disagree with its shared error policy", descriptor.identity())
	}
	seen := make(map[ErrorCode]struct{}, len(contract.Codes))
	for _, code := range contract.Codes {
		if !code.valid() {
			return fmt.Errorf("exec: built-in action %s error contract has unknown code %q", descriptor.identity(), code)
		}
		if _, duplicate := seen[code]; duplicate {
			return fmt.Errorf("exec: built-in action %s error contract repeats code %q", descriptor.identity(), code)
		}
		seen[code] = struct{}{}
		exit := contract.Exits[code]
		if exit != ErrorExitCode(code) {
			return fmt.Errorf("exec: built-in action %s error code %q disagrees with shared Cobra exit mapping", descriptor.identity(), code)
		}
	}
	return nil
}

func validateTypedHelp(descriptor actionDescriptor, descriptorCatalog *Catalog, contract auditHelpContract) error {
	if contract.ExampleCount != len(descriptor.Examples) || contract.ExampleCount == 0 || !contract.RenderedActionHelp || !contract.ClosedInputSchema || !contract.ClosedOutputSchema {
		return fmt.Errorf("exec: built-in action %s help/example contract disagrees with its registered descriptor", descriptor.identity())
	}
	help, ok := descriptorCatalog.RenderActionHelp(descriptor.Domain, descriptor.Name)
	if !ok || !strings.Contains(help, "Example:") || !strings.Contains(help, "Schemas:") {
		return fmt.Errorf("exec: built-in action %s is not rendered by the registered Cobra help contract", descriptor.identity())
	}
	return nil
}

func validateTypedCompatibility(policy builtinActionPolicy, descriptor actionDescriptor, contract auditCompatibilityContract) error {
	if strings.TrimSpace(contract.Documentation) == "" {
		return fmt.Errorf("exec: built-in action %s compatibility delta is undocumented", descriptor.identity())
	}
	switch contract.Kind {
	case auditCompatibilityProjectionNarrowing:
		if descriptor.Domain != "agent" || (descriptor.Name != "list" && descriptor.Name != "get") {
			return fmt.Errorf("exec: built-in action %s projection-narrowing compatibility is not tied to the Agent projection", descriptor.identity())
		}
	case auditCompatibilityPermissionMetadata:
		if policy.Risk != builtinRiskExistingGate {
			return fmt.Errorf("exec: built-in action %s approval metadata delta is not an existing gate", descriptor.identity())
		}
	case auditCompatibilityExisting, auditCompatibilityReplayAdditive:
	default:
		return fmt.Errorf("exec: built-in action %s has unknown compatibility contract", descriptor.identity())
	}
	return nil
}

func validateAuditPath(descriptor actionDescriptor, path auditPath) error {
	if len(path.Segments) == 0 || (path.Root != auditInputPath && path.Root != auditOutputPath) {
		return fmt.Errorf("typed path is empty or has an unknown root")
	}
	raw := descriptor.InputSchema
	if path.Root == auditOutputPath {
		raw = descriptor.OutputSchema
	}
	return resolveAuditPathSchema(raw, path.Segments)
}

func resolveAuditPathSchema(raw json.RawMessage, segments []auditPathSegment) error {
	var schema struct {
		Type       string                     `json:"type"`
		Properties map[string]json.RawMessage `json:"properties"`
		Items      json.RawMessage            `json:"items"`
		OneOf      []json.RawMessage          `json:"oneOf"`
	}
	if err := json.Unmarshal(raw, &schema); err != nil {
		return fmt.Errorf("typed path schema is invalid: %w", err)
	}
	if len(segments) == 0 {
		return nil
	}
	if strings.TrimSpace(segments[0].Name) == "" {
		return fmt.Errorf("typed path has an empty field")
	}
	if schema.Type != "object" {
		if len(schema.OneOf) > 0 {
			var last error
			for _, branch := range schema.OneOf {
				if err := resolveAuditPathSchema(branch, segments); err == nil {
					return nil
				} else {
					last = err
				}
			}
			if last != nil {
				return fmt.Errorf("typed path is absent from every closed union branch: %w", last)
			}
		}
		return fmt.Errorf("typed path enters non-object %q", schema.Type)
	}
	next, ok := schema.Properties[segments[0].Name]
	if !ok && len(schema.OneOf) > 0 {
		var last error
		for _, branch := range schema.OneOf {
			if err := resolveAuditPathSchema(branch, segments); err == nil {
				return nil
			} else {
				last = err
			}
		}
		if last != nil {
			return fmt.Errorf("typed path is absent from every closed union branch: %w", last)
		}
	}
	if !ok {
		return fmt.Errorf("typed path field %q is absent from the descriptor schema", segments[0].Name)
	}
	if segments[0].ArrayItems {
		var array struct {
			Type  string          `json:"type"`
			Items json.RawMessage `json:"items"`
		}
		if err := json.Unmarshal(next, &array); err != nil || array.Type != "array" || len(array.Items) == 0 {
			return fmt.Errorf("typed path field %q is not a closed array", segments[0].Name)
		}
		next = array.Items
	}
	return resolveAuditPathSchema(next, segments[1:])
}
