package exec

import (
	"context"
	"fmt"
	"reflect"
)

// builtinRouteRegistration is the one production assembly record for a
// built-in route. Executable is the concrete transport boundary used by the
// handler family. Capability is derived from that executable value at
// construction; callers cannot supply a second, policy-shaped capability.
type builtinRouteRegistration struct {
	Domain     string
	Action     string
	Executable builtinExecutableTransport
	Capability builtinInvokerCapability
	// install is sealed with the exact typed production operation supplied to
	// builtinRoute. Install validates the current registration copy before this
	// closure can touch an adapter, so replacing Executable cannot fall through
	// to a duplicate-registration panic or an invocation.
	install func(adapter, builtinRouteRegistration)
}

// builtinExecutableInvoker is the sealed typed production contract. Each
// family-specific concrete object owns its transport and implements Invoke;
// registration never accepts a separately supplied operation function.
type builtinExecutableInvoker[I, O any] interface {
	Capability() builtinInvokerCapability
	Executable() builtinExecutableTransport
	Invoke(context.Context, runtimeAuthorization, I) (actionResult[O], error)
}

// The family bases keep capability derivation next to the exact concrete
// transport. Concrete operation objects embed one of these bases and provide
// only their typed Invoke method.
type localReadInvoker struct{ transport localReadTransport }
type remoteReadInvoker struct{ transport remoteReadTransport }
type naturalMutationInvoker struct{ transport naturalMutationTransport }
type requestReceiptInvoker struct{ transport requestReceiptTransport }
type idempotencyReceiptInvoker struct{ transport idempotencyReceiptTransport }
type workflowReceiptInvoker struct{ transport workflowReceiptTransport }

func (inv localReadInvoker) Capability() builtinInvokerCapability    { return inv.transport.capability() }
func (inv localReadInvoker) Executable() builtinExecutableTransport  { return inv.transport }
func (inv remoteReadInvoker) Capability() builtinInvokerCapability   { return inv.transport.capability() }
func (inv remoteReadInvoker) Executable() builtinExecutableTransport { return inv.transport }
func (inv naturalMutationInvoker) Capability() builtinInvokerCapability {
	return inv.transport.capability()
}
func (inv naturalMutationInvoker) Executable() builtinExecutableTransport { return inv.transport }
func (inv requestReceiptInvoker) Capability() builtinInvokerCapability {
	return inv.transport.capability()
}
func (inv requestReceiptInvoker) Executable() builtinExecutableTransport { return inv.transport }
func (inv idempotencyReceiptInvoker) Capability() builtinInvokerCapability {
	return inv.transport.capability()
}
func (inv idempotencyReceiptInvoker) Executable() builtinExecutableTransport { return inv.transport }
func (inv workflowReceiptInvoker) Capability() builtinInvokerCapability {
	return inv.transport.capability()
}
func (inv workflowReceiptInvoker) Executable() builtinExecutableTransport { return inv.transport }
func builtinRoute[I, O any](domain, action string, executable builtinExecutableInvoker[I, O]) builtinRouteRegistration {
	if executable == nil || executable.Executable() == nil {
		panic("exec: incomplete executable invoker for " + domain + " " + action)
	}
	transport := executable.Executable()
	capability := executable.Capability().withRoute(domain, action)
	return builtinRouteRegistration{
		Domain: domain, Action: action, Executable: transport, Capability: capability,
		install: func(adapter adapter, registration builtinRouteRegistration) {
			if !sameExecutableTransport(registration.Executable, transport) {
				panic(fmt.Sprintf("exec: %s executable transport was replaced after sealing", builtinActionKey(domain, action)))
			}
			registerJSONWithOwnedInvoker(nil, adapter, domain, action, executable, registration.Capability)
		},
	}
}

func (registration builtinRouteRegistration) Install(adapter adapter) {
	if err := registration.validate(); err != nil {
		panic(err)
	}
	if registration.install == nil {
		panic(fmt.Sprintf("exec: %s registration has no sealed install operation", builtinActionKey(registration.Domain, registration.Action)))
	}
	registration.install(adapter, registration)
}

func sameExecutableTransport(left, right builtinExecutableTransport) bool {
	if left == nil || right == nil || reflect.TypeOf(left) != reflect.TypeOf(right) {
		return false
	}
	return reflect.DeepEqual(left, right)
}

func (registration builtinRouteRegistration) validate() error {
	key := builtinActionKey(registration.Domain, registration.Action)
	if registration.Executable == nil {
		return fmt.Errorf("exec: %s lacks executable transport", key)
	}
	if registration.install == nil {
		return fmt.Errorf("exec: %s lacks sealed install operation", key)
	}
	attested := registration.Executable.capability(registration.Capability.SelectorPaths...).withRoute(registration.Domain, registration.Action)
	if !capabilityEqual(registration.Capability, attested) {
		return fmt.Errorf("exec: %s executable transport capability mismatch", key)
	}
	return registration.Capability.validate(key)
}

// builtinRouteRegistrations assembles the concrete route handlers and their
// executable transport boundaries together. It is used both by daemon startup
// and by catalog governance validation, so a removed or downgraded handler
// cannot leave a stale audit policy behind.
func builtinRouteRegistrations(
	agent agentHandler,
	team teamHandler,
	channel channelHandler,
	profile profileHandler,
	project projectHandler,
	review reviewHandler,
	role roleHandler,
	skillHandlerValue skillHandler,
	scheduled scheduledMessageHandler,
	task taskHandler,
	workflow workflowHandler,
	workspace workspaceHandler,
) []builtinRouteRegistration {
	// Agent.Get is the only local read whose production handler can emit a
	// typed not-found result; keep that distinction and the local source on the
	// executable transport instance used by both registration and invocation.
	if agent.local.source == nil {
		agent.local.source = agent.source
	}
	if agent.localGet.source == nil {
		agent.localGet.source = agent.source
	}
	agent.localGet.hasNotFound = true
	workflowAction := workflow.receipt
	workflowAction.selectorPaths = []auditPath{inputPath("action")}
	workflowStep := workflow.receipt
	workflowStep.selectorPaths = []auditPath{inputPath("action"), inputPath("runId"), inputPath("stepId"), inputPath("idempotencyKey")}
	taskCreateNatural := task.natural
	taskCreateNatural.turnTargets = task.turnTargets
	return []builtinRouteRegistration{
		builtinRoute[agentListRequest, agentListResult]("agent", "list", invokeAgentListObject{localReadInvoker{transport: agent.local}}),
		builtinRoute[agentGetRequest, agentGetResult]("agent", "get", invokeAgentGetObject{localReadInvoker{transport: agent.localGet}}),
		builtinRoute[agentLifecycleRequest, agentStartResponse]("agent", "start", invokeAgentStartObject{requestReceiptInvoker{transport: agent.receipt}}),
		builtinRoute[agentLifecycleRequest, agentStopOutcome]("agent", "stop", invokeAgentStopObject{requestReceiptInvoker{transport: agent.receipt}}),
		builtinRoute[agentLifecycleRequest, agentRestartResponse]("agent", "restart", invokeAgentRestartObject{requestReceiptInvoker{transport: agent.receipt}}),
		builtinRoute[agentCreateRequest, agentStructuralResponse]("agent", "create", invokeAgentCreateObject{requestReceiptInvoker{transport: agent.receipt}}),
		builtinRoute[agentUpdateRequest, agentStructuralResponse]("agent", "update", invokeAgentUpdateObject{requestReceiptInvoker{transport: agent.receipt}}),
		builtinRoute[agentDeleteRequest, agentStructuralResponse]("agent", "delete", invokeAgentDeleteObject{requestReceiptInvoker{transport: agent.receipt}}),
		builtinRoute[agentDuplicateRequest, agentStructuralResponse]("agent", "duplicate", invokeAgentDuplicateObject{requestReceiptInvoker{transport: agent.receipt}}),

		builtinRoute[teamListInput, teamListOutput]("team", "list", invokeTeamListObject{remoteReadInvoker{transport: team.read}}),
		builtinRoute[teamGetInput, teamGetOutput]("team", "get", invokeTeamGetObject{remoteReadInvoker{transport: team.read}}),
		builtinRoute[teamUpdateInput, teamUpdateOutput]("team", "update", invokeTeamUpdateObject{requestReceiptInvoker{transport: team.receipt}}),
		builtinRoute[channelListInput, channelListOutput]("channel", "list", invokeChannelListObject{remoteReadInvoker{transport: channel.read}}),
		builtinRoute[channelGetInput, channelGetOutput]("channel", "get", invokeChannelGetObject{remoteReadInvoker{transport: channel.read}}),
		builtinRoute[channelMembersInput, channelMembersOutput]("channel", "members", invokeChannelMembersObject{remoteReadInvoker{transport: channel.read}}),
		builtinRoute[channelCreateInput, channelCreateOutput]("channel", "create", invokeChannelCreateObject{requestReceiptInvoker{transport: channel.receipt}}),
		builtinRoute[channelUpdateInput, channelUpdateOutput]("channel", "update", invokeChannelUpdateObject{requestReceiptInvoker{transport: channel.receipt}}),
		builtinRoute[channelAgentAddInput, channelAgentMutationOutput]("channel", "agent-add", invokeChannelAgentAddObject{requestReceiptInvoker{transport: channel.receipt}}),
		builtinRoute[channelAgentRemoveInput, channelAgentMutationOutput]("channel", "agent-remove", invokeChannelAgentRemoveObject{requestReceiptInvoker{transport: channel.receipt}}),
		builtinRoute[profileSetAvatarRequest, profileSetAvatarResult]("profile", "set-avatar", invokeProfileSetAvatarObject{naturalMutationInvoker{transport: profile.natural}}),
		builtinRoute[projectListRequest, projectListResponse]("project", "list", invokeProjectListObject{remoteReadInvoker{transport: project.read}}),
		builtinRoute[projectCreateRequestDTO, projectCreateResponse]("project", "create", invokeProjectCreateObject{naturalMutationInvoker{transport: project.natural}}),
		builtinRoute[projectDeleteRequest, projectDeleteResponse]("project", "delete", invokeProjectDeleteObject{naturalMutationInvoker{transport: project.natural}}),
		builtinRoute[reviewAddInput, reviewAddResult]("review", "add", invokeReviewAddObject{naturalMutationInvoker{transport: review.natural}}),
		builtinRoute[reviewListRequest, reviewListResult]("review", "list", invokeReviewListObject{remoteReadInvoker{transport: review.read}}),
		builtinRoute[reviewDeleteRequest, reviewDeleteResult]("review", "delete", invokeReviewDeleteObject{naturalMutationInvoker{transport: review.natural}}),

		builtinRoute[roleListRequest, roleListResult]("role", "list", invokeRoleListObject{remoteReadInvoker{transport: role.read}}),
		builtinRoute[roleGetRequest, roleGetResult]("role", "get", invokeRoleGetObject{remoteReadInvoker{transport: role.read}}),
		builtinRoute[roleRequestGetRequest, roleRequestGetResult]("role", "request-get", invokeRoleRequestGetObject{remoteReadInvoker{transport: role.read}}),
		builtinRoute[roleCreateRequest, roleRequestResponse]("role", "create", invokeRoleCreateObject{requestReceiptInvoker{transport: role.receipt}}),
		builtinRoute[roleUpdateRequest, roleRequestResponse]("role", "update", invokeRoleUpdateObject{requestReceiptInvoker{transport: role.receipt}}),
		builtinRoute[roleDeleteRequest, roleRequestResponse]("role", "delete", invokeRoleDeleteObject{requestReceiptInvoker{transport: role.receipt}}),
		builtinRoute[roleApplyRequest, roleRequestResponse]("role", "apply", invokeRoleApplyObject{requestReceiptInvoker{transport: role.receipt}}),

		builtinRoute[skillListRequest, skillListResult]("skill", "list", invokeSkillListObject{remoteReadInvoker{transport: skillHandlerValue.read}}),
		builtinRoute[skillGetRequest, skill]("skill", "get", invokeSkillGetObject{remoteReadInvoker{transport: skillHandlerValue.read}}),
		builtinRoute[skillCreateRequest, skill]("skill", "create", invokeSkillCreateObject{naturalMutationInvoker{transport: skillHandlerValue.natural}}),
		builtinRoute[skillUpdateRequest, skill]("skill", "update", invokeSkillUpdateObject{naturalMutationInvoker{transport: skillHandlerValue.natural}}),
		builtinRoute[skillImportRequest, skillImportResult]("skill", "import", invokeSkillImportObject{naturalMutationInvoker{transport: skillHandlerValue.natural}}),
		builtinRoute[skillDeleteRequest, skillDeleteResult]("skill", "delete", invokeSkillDeleteObject{requestReceiptInvoker{transport: skillHandlerValue.receipt}}),

		builtinRoute[scheduledMessageCreateRequest, scheduledMessageRecord]("scheduled-message", "create", invokeScheduledMessageCreateObject{idempotencyReceiptInvoker{transport: scheduled.idempotency}}),
		builtinRoute[scheduledMessageIDRequest, scheduledMessageDeleteResult]("scheduled-message", "delete", invokeScheduledMessageDeleteObject{naturalMutationInvoker{transport: scheduled.natural}}),
		builtinRoute[scheduledMessageIDRequest, scheduledMessageRecord]("scheduled-message", "get", invokeScheduledMessageGetObject{remoteReadInvoker{transport: scheduled.read}}),
		builtinRoute[scheduledMessageListRequest, scheduledMessageListResult]("scheduled-message", "list", invokeScheduledMessageListObject{remoteReadInvoker{transport: scheduled.read}}),
		builtinRoute[scheduledMessageIDRequest, scheduledMessageRecord]("scheduled-message", "pause", invokeScheduledMessagePauseObject{naturalMutationInvoker{transport: scheduled.natural}}),
		builtinRoute[scheduledMessageIDRequest, scheduledMessageRecord]("scheduled-message", "resume", invokeScheduledMessageResumeObject{naturalMutationInvoker{transport: scheduled.natural}}),
		builtinRoute[scheduledMessageRunsRequest, scheduledMessageRunsResult]("scheduled-message", "runs", invokeScheduledMessageRunsObject{remoteReadInvoker{transport: scheduled.read}}),
		builtinRoute[scheduledMessageUpdateRequest, scheduledMessageRecord]("scheduled-message", "update", invokeScheduledMessageUpdateObject{idempotencyReceiptInvoker{transport: scheduled.idempotency}}),
		builtinRoute[scheduledMessageValidateRequest, scheduledMessageValidationResult]("scheduled-message", "validate", invokeScheduledMessageValidateObject{remoteReadInvoker{transport: scheduled.read}}),

		builtinRoute[taskCheckpointRequest, taskMutationResult]("task", "checkpoint", invokeTaskCheckpointObject{naturalMutationInvoker{transport: task.natural}}),
		builtinRoute[taskClaimRequest, taskClaimResult]("task", "claim", invokeTaskClaimObject{naturalMutationInvoker{transport: task.natural}}),
		builtinRoute[taskCreateRequest, taskCreateResult]("task", "create", invokeTaskCreateObject{naturalMutationInvoker{transport: taskCreateNatural}}),
		builtinRoute[taskDeleteRequest, taskDeleteResult]("task", "delete", invokeTaskDeleteObject{naturalMutationInvoker{transport: task.natural}}),
		builtinRoute[taskGetRequest, taskDetailResult]("task", "get", invokeTaskGetObject{remoteReadInvoker{transport: task.read}}),
		builtinRoute[taskListRequest, taskListResult]("task", "list", invokeTaskListObject{remoteReadInvoker{transport: task.read}}),
		builtinRoute[taskClaimRequest, taskClaimResult]("task", "unclaim", invokeTaskUnclaimObject{naturalMutationInvoker{transport: task.natural}}),
		builtinRoute[taskUpdateRequest, taskMutationResult]("task", "update", invokeTaskUpdateObject{naturalMutationInvoker{transport: task.natural}}),

		builtinRoute[workflowPlanRequest, workflowPlanResult]("workflow", "plan", invokeWorkflowPlanObject{workflowReceiptInvoker{transport: workflowAction}}),
		builtinRoute[workflowTriggerRequest, workflowTriggerResult]("workflow", "trigger", invokeWorkflowTriggerObject{workflowReceiptInvoker{transport: workflowAction}}),
		builtinRoute[workflowRunRequest, workflowRunResult]("workflow", "run", invokeWorkflowRunObject{workflowReceiptInvoker{transport: workflowAction}}),
		builtinRoute[workflowStepCommand, workflowStepOutcome]("workflow", "step", invokeWorkflowStepObject{workflowReceiptInvoker{transport: workflowStep}}),
		builtinRoute[workspaceListRequest, workspaceListResponse]("workspace", "list", invokeWorkspaceListObject{remoteReadInvoker{transport: workspace.read}}),
	}
}

func builtinCapabilityForAction(key string) (builtinInvokerCapability, bool) {
	capability, ok := builtinRouteCapabilities()[key]
	return capability, ok
}

func builtinRouteCapabilities() map[string]builtinInvokerCapability {
	registrations := builtinRouteRegistrations(
		agentHandler{}, teamHandler{}, channelHandler{}, profileHandler{}, projectHandler{}, reviewHandler{},
		roleHandler{}, skillHandler{}, scheduledMessageHandler{}, taskHandler{}, workflowHandler{}, workspaceHandler{},
	)
	out := make(map[string]builtinInvokerCapability, len(registrations))
	for _, registration := range registrations {
		key := builtinActionKey(registration.Domain, registration.Action)
		if _, exists := out[key]; exists {
			continue
		}
		out[key] = registration.Capability
	}
	return out
}
