package wire

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

type desktopRemoteEndpointHandler func(context.Context, *Protocol, remoteRequestAssignmentMsg) (json.RawMessage, string, error)

type desktopRemoteEndpointDescriptor struct {
	EndpointID string
	Version    int
	Handler    desktopRemoteEndpointHandler
}

type desktopRemoteEndpointRegistry struct {
	byKey map[string]desktopRemoteEndpointDescriptor
}

func newDesktopRemoteEndpointRegistry(descriptors []desktopRemoteEndpointDescriptor) *desktopRemoteEndpointRegistry {
	registry := &desktopRemoteEndpointRegistry{byKey: make(map[string]desktopRemoteEndpointDescriptor, len(descriptors))}
	for _, descriptor := range descriptors {
		descriptor.EndpointID = strings.TrimSpace(descriptor.EndpointID)
		if descriptor.EndpointID == "" || descriptor.Version <= 0 || descriptor.Handler == nil {
			continue
		}
		registry.byKey[desktopRemoteEndpointKey(descriptor.EndpointID, descriptor.Version)] = descriptor
	}
	return registry
}

func defaultDesktopRemoteEndpointRegistry() *desktopRemoteEndpointRegistry {
	return newDesktopRemoteEndpointRegistry([]desktopRemoteEndpointDescriptor{
		{
			EndpointID: remoteEndpointWorkspaceList,
			Version:    1,
			Handler:    handleRemoteWorkspaceList,
		},
		{
			EndpointID: remoteEndpointWorkspaceRead,
			Version:    1,
			Handler:    handleRemoteWorkspaceRead,
		},
		{
			EndpointID: remoteEndpointWorkspaceApplySeedFiles,
			Version:    1,
			Handler:    handleRemoteWorkspaceApplySeedFiles,
		},
		{
			EndpointID: remoteEndpointWorkspaceDelete,
			Version:    1,
			Handler:    handleRemoteWorkspaceDelete,
		},
		{
			EndpointID: remoteEndpointReviewSummary,
			Version:    1,
			Handler:    handleRemoteReviewSummary,
		},
		{
			EndpointID: remoteEndpointReviewSnapshot,
			Version:    1,
			Handler:    handleRemoteReviewSnapshot,
		},
		{
			EndpointID: remoteEndpointReviewFingerprint,
			Version:    1,
			Handler:    handleRemoteReviewFingerprint,
		},
		{
			EndpointID: remoteEndpointReviewFiles,
			Version:    1,
			Handler:    handleRemoteReviewFiles,
		},
		{
			EndpointID: remoteEndpointReviewDiff,
			Version:    1,
			Handler:    handleRemoteReviewDiff,
		},
		{
			EndpointID: remoteEndpointReviewDiffBatch,
			Version:    1,
			Handler:    handleRemoteReviewDiffBatch,
		},
		{
			EndpointID: remoteEndpointOutputTargetValidate,
			Version:    1,
			Handler:    handleRemoteOutputTargetValidate,
		},
		{
			EndpointID: remoteEndpointMachineWorkspaceScan,
			Version:    1,
			Handler:    handleRemoteMachineWorkspaceScan,
		},
		{
			EndpointID: remoteEndpointMachineWorkspaceDelete,
			Version:    1,
			Handler:    handleRemoteMachineWorkspaceDelete,
		},
		{
			EndpointID: remoteEndpointMachineDirectoryList,
			Version:    1,
			Handler:    handleRemoteMachineDirectoryList,
		},
		{
			EndpointID: remoteEndpointMachineDashboard,
			Version:    remoteEndpointMachineDashboardVersion,
			Handler:    handleRemoteMachineDashboard,
		},
		{
			EndpointID: remoteEndpointMachineSettings,
			Version:    1,
			Handler:    handleRemoteMachineSettings,
		},
		{
			EndpointID: remoteEndpointMachineSettingsUpdate,
			Version:    1,
			Handler:    handleRemoteMachineSettingsUpdate,
		},
		{
			EndpointID: remoteEndpointMachineRuntimesBundle,
			Version:    1,
			Handler:    handleRemoteMachineRuntimesBundle,
		},
		{
			EndpointID: remoteEndpointMachineRuntimeConfigRead,
			Version:    1,
			Handler:    handleRemoteMachineRuntimeConfigRead,
		},
		{
			EndpointID: remoteEndpointMachineRuntimeConfigUpdate,
			Version:    1,
			Handler:    handleRemoteMachineRuntimeConfigUpdate,
		},
		{
			EndpointID: remoteEndpointMachineRuntimeConfigValidate,
			Version:    1,
			Handler:    handleRemoteMachineRuntimeConfigValidate,
		},
		{
			EndpointID: remoteEndpointMachineRuntimesDetect,
			Version:    1,
			Handler:    handleRemoteMachineRuntimesDetect,
		},
		{
			EndpointID: remoteEndpointMachineMCPServerList,
			Version:    1,
			Handler:    handleRemoteMachineMCPServerList,
		},
		{
			EndpointID: remoteEndpointMachineMCPServerUpsert,
			Version:    1,
			Handler:    handleRemoteMachineMCPServerUpsert,
		},
		{
			EndpointID: remoteEndpointMachineMCPServerDelete,
			Version:    1,
			Handler:    handleRemoteMachineMCPServerDelete,
		},
		{
			EndpointID: remoteEndpointMachineMCPServerTest,
			Version:    1,
			Handler:    handleRemoteMachineMCPServerTest,
		},
		{
			EndpointID: remoteEndpointRuntimeTrajectoryHistory,
			Version:    1,
			Handler:    handleRemoteRuntimeTrajectoryHistory,
		},
		{
			EndpointID: remoteEndpointRuntimeRestoreRecoveryPoint,
			Version:    1,
			Handler:    handleRemoteRuntimeRestoreRecoveryPoint,
		},
		{
			EndpointID: remoteEndpointRuntimeMemorySyncStatus,
			Version:    1,
			Handler:    handleRemoteRuntimeMemorySyncStatus,
		},
		{
			EndpointID: remoteEndpointRuntimeMemorySyncRetry,
			Version:    1,
			Handler:    handleRemoteRuntimeMemorySyncRetry,
		},
	})
}

func (registry *desktopRemoteEndpointRegistry) lookup(endpointID string, version int) (desktopRemoteEndpointDescriptor, bool) {
	if registry == nil {
		return desktopRemoteEndpointDescriptor{}, false
	}
	descriptor, ok := registry.byKey[desktopRemoteEndpointKey(endpointID, version)]
	return descriptor, ok
}

func (registry *desktopRemoteEndpointRegistry) supportedEndpoints() []daemonTransportEndpointRef {
	if registry == nil || len(registry.byKey) == 0 {
		return nil
	}
	endpoints := make([]daemonTransportEndpointRef, 0, len(registry.byKey))
	for _, descriptor := range registry.byKey {
		endpoints = append(endpoints, daemonTransportEndpointRef{EndpointID: descriptor.EndpointID, Version: descriptor.Version})
	}
	sort.Slice(endpoints, func(i, j int) bool {
		if endpoints[i].EndpointID == endpoints[j].EndpointID {
			return endpoints[i].Version < endpoints[j].Version
		}
		return endpoints[i].EndpointID < endpoints[j].EndpointID
	})
	return endpoints
}

func desktopRemoteEndpointKey(endpointID string, version int) string {
	endpointID = strings.TrimSpace(endpointID)
	return endpointID + "@" + fmt.Sprint(version)
}

// allowedEndpointSet mirrors the Server grant allowlist on the Desktop side.
// A connected Elastic Request Link may execute only assignments whose
// endpoint/version was present in the open command; the token does not grant a
// general-purpose request tunnel.
func allowedEndpointSet(endpoints []daemonTransportEndpointRef) map[string]struct{} {
	allowed := make(map[string]struct{}, len(endpoints))
	for _, endpoint := range endpoints {
		if strings.TrimSpace(endpoint.EndpointID) == "" || endpoint.Version <= 0 {
			continue
		}
		allowed[desktopRemoteEndpointKey(endpoint.EndpointID, endpoint.Version)] = struct{}{}
	}
	return allowed
}

// desktopRemoteEndpointAllowsWarmCarrier mirrors the server's default warm
// carrier profile as a Desktop-side defense-in-depth check. Demand grants still
// use the assignment envelope as the endpoint execution authority; warm opens
// must not accidentally pre-authorize destructive or demand-only endpoints.
func desktopRemoteEndpointAllowsWarmCarrier(endpointID string, version int) bool {
	if version != 1 {
		return false
	}
	switch strings.TrimSpace(endpointID) {
	case remoteEndpointWorkspaceList,
		remoteEndpointWorkspaceRead,
		remoteEndpointReviewSummary,
		remoteEndpointReviewSnapshot,
		remoteEndpointReviewFingerprint,
		remoteEndpointReviewFiles,
		remoteEndpointReviewDiff,
		remoteEndpointReviewDiffBatch,
		remoteEndpointOutputTargetValidate,
		remoteEndpointMachineWorkspaceScan,
		remoteEndpointRuntimeTrajectoryHistory:
		return true
	default:
		return false
	}
}
