package exec

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

// builtinInvokerCapability is production-attested metadata attached to the
// registered invoker, not an audit policy guess. The capability describes the
// concrete client boundary used by the handler: its receipt transport, retry
// mode, selector/digest fence, and the typed errors the shared daemon/Cobra
// projection can emit for that route.
// builtinExecutableTransport is the attestation boundary between route
// assembly and the concrete client method. A registration receives one of
// the production transport implementations below; its capability is derived
// from that executable value rather than copied into a parallel declaration.
type builtinExecutableTransport interface {
	capability(...auditPath) builtinInvokerCapability
}

type localReadTransport struct {
	hasNotFound bool
	source      AgentReader
}

func (transport localReadTransport) capability(_ ...auditPath) builtinInvokerCapability {
	return localReadCapability(transport.hasNotFound)
}

type builtinInvokerCapability struct {
	Transport       builtinTransportCapability
	Retry           builtinRetryCapability
	Selector        builtinSelectorCapability
	Digest          builtinDigestCapability
	SelectorPaths   []auditPath
	ErrorPolicy     builtinErrorPolicyIdentity
	ErrorProjection string
	Route           string
}

type builtinTransportCapability string

type builtinRetryCapability string

type builtinSelectorCapability string

type builtinDigestCapability string

const (
	builtinTransportLocalRead       builtinTransportCapability = "local_read"
	builtinTransportRemoteRead      builtinTransportCapability = "remote_read"
	builtinTransportNaturalMutation builtinTransportCapability = "natural_mutation"
	builtinTransportRequestReceipt  builtinTransportCapability = "request_id_receipt"
	builtinTransportIdempotency     builtinTransportCapability = "idempotency_receipt"
	builtinTransportWorkflow        builtinTransportCapability = "workflow_receipt"

	builtinRetryNone      builtinRetryCapability = "none"
	builtinRetryReadSafe  builtinRetryCapability = "bounded_transient_read"
	builtinRetryTransient builtinRetryCapability = "bounded_transient_receipt"
	builtinRetryWorkflow  builtinRetryCapability = "workflow_callback_boundary"

	builtinSelectorNone        builtinSelectorCapability = "none"
	builtinSelectorNatural     builtinSelectorCapability = "natural_resource"
	builtinSelectorRequestID   builtinSelectorCapability = "request_id"
	builtinSelectorIdempotency builtinSelectorCapability = "idempotency_key"
	builtinSelectorWorkflow    builtinSelectorCapability = "workflow_operation"

	builtinDigestNone     builtinDigestCapability = "none"
	builtinDigestRequest  builtinDigestCapability = "canonical_request"
	builtinDigestWrite    builtinDigestCapability = "canonical_write"
	builtinDigestResource builtinDigestCapability = "authoritative_resource"
)

func capability(transport builtinTransportCapability, retry builtinRetryCapability, selector builtinSelectorCapability, digest builtinDigestCapability, paths []auditPath, policy builtinErrorPolicyIdentity) builtinInvokerCapability {
	return builtinInvokerCapability{
		Transport: transport, Retry: retry, Selector: selector, Digest: digest,
		SelectorPaths: append([]auditPath(nil), paths...), ErrorPolicy: policy,
		ErrorProjection: builtinErrorProjectorDigest(),
	}
}

func localReadCapability(hasNotFound bool) builtinInvokerCapability {
	policy := builtinErrorPolicyLocalRead
	if hasNotFound {
		policy = builtinErrorPolicyLocalGet
	}
	return capability(builtinTransportLocalRead, builtinRetryNone, builtinSelectorNone, builtinDigestNone, nil, policy)
}

func remoteReadCapability() builtinInvokerCapability {
	// apiclient.Client.Get owns the bounded retry-safe read boundary. A route
	// cannot downgrade that concrete method to "no retry" in audit metadata.
	return capability(builtinTransportRemoteRead, builtinRetryReadSafe, builtinSelectorNone, builtinDigestNone, nil, builtinErrorPolicyRemoteRead)
}

func naturalMutationCapability() builtinInvokerCapability {
	return capability(builtinTransportNaturalMutation, builtinRetryNone, builtinSelectorNatural, builtinDigestResource, nil, builtinErrorPolicyNaturalMutation)
}

func requestReceiptCapability(paths ...auditPath) builtinInvokerCapability {
	return capability(builtinTransportRequestReceipt, builtinRetryTransient, builtinSelectorRequestID, builtinDigestRequest, paths, builtinErrorPolicyReceiptMutation)
}

func idempotencyCapability(paths ...auditPath) builtinInvokerCapability {
	return capability(builtinTransportIdempotency, builtinRetryTransient, builtinSelectorIdempotency, builtinDigestWrite, paths, builtinErrorPolicyReceiptMutation)
}

func workflowCapability(paths ...auditPath) builtinInvokerCapability {
	return capability(builtinTransportWorkflow, builtinRetryWorkflow, builtinSelectorWorkflow, builtinDigestWrite, paths, builtinErrorPolicyWorkflowMutation)
}

func (cap builtinInvokerCapability) withRoute(domain, action string) builtinInvokerCapability {
	cap.Route = "/servers/:serverID/agents/:agentID/" + domain + "/" + action
	return cap
}

func (cap builtinInvokerCapability) validate(key string) error {
	if strings.TrimSpace(string(cap.Transport)) == "" || strings.TrimSpace(string(cap.Retry)) == "" ||
		strings.TrimSpace(string(cap.Selector)) == "" || strings.TrimSpace(string(cap.Digest)) == "" {
		return fmt.Errorf("exec: built-in action %s has incomplete registered invoker capability", key)
	}
	if strings.TrimSpace(cap.Route) == "" {
		return fmt.Errorf("exec: built-in action %s registered invoker capability lacks route", key)
	}
	switch cap.Transport {
	case builtinTransportLocalRead:
		if cap.Retry != builtinRetryNone || cap.Selector != builtinSelectorNone || cap.Digest != builtinDigestNone || len(cap.SelectorPaths) != 0 {
			return fmt.Errorf("exec: built-in action %s local-read capability has mutation metadata", key)
		}
	case builtinTransportRemoteRead:
		if (cap.Retry != builtinRetryNone && cap.Retry != builtinRetryReadSafe) || cap.Selector != builtinSelectorNone || cap.Digest != builtinDigestNone || len(cap.SelectorPaths) != 0 {
			return fmt.Errorf("exec: built-in action %s remote-read capability has mutation metadata", key)
		}
	case builtinTransportNaturalMutation:
		if cap.Retry != builtinRetryNone || cap.Selector != builtinSelectorNatural || cap.Digest != builtinDigestResource || len(cap.SelectorPaths) != 0 {
			return fmt.Errorf("exec: built-in action %s natural-mutation capability is not fail-closed", key)
		}
	case builtinTransportRequestReceipt:
		if cap.Retry != builtinRetryTransient || cap.Selector != builtinSelectorRequestID || cap.Digest != builtinDigestRequest || len(cap.SelectorPaths) == 0 {
			return fmt.Errorf("exec: built-in action %s request-receipt capability is incomplete", key)
		}
	case builtinTransportIdempotency:
		if cap.Retry != builtinRetryTransient || cap.Selector != builtinSelectorIdempotency || cap.Digest != builtinDigestWrite || len(cap.SelectorPaths) == 0 {
			return fmt.Errorf("exec: built-in action %s idempotency-receipt capability is incomplete", key)
		}
	case builtinTransportWorkflow:
		if cap.Retry != builtinRetryWorkflow || cap.Selector != builtinSelectorWorkflow || cap.Digest != builtinDigestWrite || len(cap.SelectorPaths) == 0 {
			return fmt.Errorf("exec: built-in action %s Workflow-receipt capability is incomplete", key)
		}
	default:
		return fmt.Errorf("exec: built-in action %s has unknown registered transport %q", key, cap.Transport)
	}
	if !cap.ErrorPolicy.valid() {
		return fmt.Errorf("exec: built-in action %s registered invoker capability has unknown error policy %q", key, cap.ErrorPolicy)
	}
	if cap.ErrorProjection == "" || cap.ErrorProjection != builtinErrorProjectorDigest() {
		return fmt.Errorf("exec: built-in action %s registered invoker capability has stale error projector %q", key, cap.ErrorProjection)
	}
	codes := cap.ErrorPolicy.codes()
	if len(codes) == 0 {
		return fmt.Errorf("exec: built-in action %s registered invoker capability error policy is empty", key)
	}
	seen := make(map[ErrorCode]struct{}, len(codes))
	for _, code := range codes {
		if !code.valid() {
			return fmt.Errorf("exec: built-in action %s registered error policy has unknown error class %q", key, code)
		}
		if _, exists := seen[code]; exists {
			return fmt.Errorf("exec: built-in action %s registered error policy repeats error class %q", key, code)
		}
		seen[code] = struct{}{}
	}
	return nil
}

func (cap builtinInvokerCapability) replayPolicy() builtinMutationReplayPolicy {
	switch cap.Transport {
	case builtinTransportLocalRead, builtinTransportRemoteRead:
		return builtinReplayNotApplicable
	case builtinTransportNaturalMutation:
		return builtinReplayNaturalKey
	case builtinTransportRequestReceipt:
		return builtinReplayRequestID
	case builtinTransportIdempotency:
		return builtinReplayIdempotency
	case builtinTransportWorkflow:
		return builtinReplayWorkflow
	default:
		return ""
	}
}

func (cap builtinInvokerCapability) auditTransport() auditTransportKind {
	switch cap.Transport {
	case builtinTransportLocalRead:
		return auditTransportLocalRead
	case builtinTransportRemoteRead:
		return auditTransportRemoteRead
	case builtinTransportNaturalMutation:
		return auditTransportNaturalMutation
	case builtinTransportRequestReceipt:
		return auditTransportRequestReceipt
	case builtinTransportIdempotency:
		return auditTransportIdempotencyReceipt
	case builtinTransportWorkflow:
		return auditTransportWorkflowReceipt
	default:
		return 0
	}
}

func (cap builtinInvokerCapability) auditDigest() auditDigestKind {
	switch cap.Digest {
	case builtinDigestNone:
		return auditDigestNone
	case builtinDigestRequest:
		return auditDigestCanonicalRequest
	case builtinDigestWrite:
		return auditDigestCanonicalWrite
	case builtinDigestResource:
		return auditDigestAuthoritativeResource
	default:
		return 0
	}
}

func (cap builtinInvokerCapability) auditRetry() auditRetryKind {
	switch cap.Retry {
	case builtinRetryNone:
		return auditRetryNone
	case builtinRetryReadSafe:
		return auditRetryReadSafe
	case builtinRetryTransient:
		return auditRetryOneTransient
	case builtinRetryWorkflow:
		return auditRetryReceiptOnly
	default:
		return 0
	}
}

func sortedErrorCodes(codes []ErrorCode) []ErrorCode {
	out := append([]ErrorCode(nil), codes...)
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}

func equalAuditPaths(left, right []auditPath) bool {
	if len(left) != len(right) {
		return false
	}
	for i := range left {
		if left[i].Root != right[i].Root || len(left[i].Segments) != len(right[i].Segments) {
			return false
		}
		for j := range left[i].Segments {
			if left[i].Segments[j] != right[i].Segments[j] {
				return false
			}
		}
	}
	return true
}

func equalErrorCodes(left, right []ErrorCode) bool {
	left, right = sortedErrorCodes(left), sortedErrorCodes(right)
	if len(left) != len(right) {
		return false
	}
	for i := range left {
		if left[i] != right[i] {
			return false
		}
	}
	return true
}
