package daemon

import (
	"errors"
	"strings"
)

type RemoteRequestRouteMode string

const (
	RemoteRequestRouteSessionLink               RemoteRequestRouteMode = "session_link"
	RemoteRequestRouteElasticRequestLink        RemoteRequestRouteMode = "elastic_request_link"
	RemoteRequestRouteOpenElasticRequestLink    RemoteRequestRouteMode = "open_elastic_request_link"
	RemoteRequestRouteLegacySessionLinkFallback RemoteRequestRouteMode = "legacy_session_link_fallback"
	RemoteRequestRouteStableError               RemoteRequestRouteMode = "stable_error"
)

type RemoteRequestStableError string

const (
	RemoteRequestStableErrorDesktopBusy        RemoteRequestStableError = "desktop_busy"
	RemoteRequestStableErrorDesktopUnavailable RemoteRequestStableError = "desktop_unavailable"
)

type RemoteRequestRouteOptions struct {
	SessionLinkUnderPressure bool
	ForceElastic             bool
	EndpointVersion          int
}

type RemoteRequestRouteDecision struct {
	Mode        RemoteRequestRouteMode
	Reason      string
	StableError RemoteRequestStableError
	LinkID      string
	OpenCommand *ElasticRequestLinkOpenCommand
}

// DecideRoute applies endpoint LinkPolicy without changing the caller-visible
// Web/API contract. Missing capability, registry mismatch, capacity, or unhealthy
// Elastic Request Link state either picks the endpoint's legacy Session Link
// fallback, the typed session-link data-plane envelope, or returns a stable
// business error.
func (manager *ElasticRequestLinkManager) DecideRoute(client *clientState, descriptor RemoteEndpointDescriptor, opts RemoteRequestRouteOptions) RemoteRequestRouteDecision {
	record := func(decision RemoteRequestRouteDecision) RemoteRequestRouteDecision {
		manager.recordRouteDecision(descriptor, decision)
		return decision
	}
	if descriptor.Version <= 0 {
		descriptor.Version = opts.EndpointVersion
	}
	if descriptor.Timeout <= 0 || descriptor.DialTimeout <= 0 {
		return record(stableOrFallbackDecision(client, descriptor, "invalid_endpoint_timeout", RemoteRequestStableErrorDesktopUnavailable))
	}
	if descriptor.LinkPolicy == RemoteEndpointLinkPolicySessionOnly {
		return record(RemoteRequestRouteDecision{Mode: RemoteRequestRouteSessionLink, Reason: "session_only"})
	}
	if descriptor.LinkPolicy == RemoteEndpointLinkPolicySessionPreferred && !opts.SessionLinkUnderPressure && !opts.ForceElastic {
		if client == nil || strings.TrimSpace(client.currentSessionGeneration()) == "" {
			return record(stableOrFallbackDecision(client, descriptor, "session_link_unavailable", RemoteRequestStableErrorDesktopUnavailable))
		}
		if !client.supportsSessionRemoteRequests() {
			return record(stableOrFallbackDecision(client, descriptor, "missing_transport_capability", RemoteRequestStableErrorDesktopUnavailable))
		}
		if !client.supportsRemoteEndpoint(descriptor.EndpointID, descriptor.Version, descriptor.RegistryVersion) {
			return record(stableOrFallbackDecision(client, descriptor, "endpoint_registry_mismatch", RemoteRequestStableErrorDesktopUnavailable))
		}
		return record(RemoteRequestRouteDecision{Mode: RemoteRequestRouteSessionLink, Reason: "session_link_within_policy"})
	}
	if manager == nil || manager.config.EmergencyDisabled {
		return record(stableOrFallbackDecision(client, descriptor, "elastic_request_disabled", RemoteRequestStableErrorDesktopUnavailable))
	}
	if client == nil || strings.TrimSpace(client.currentSessionGeneration()) == "" {
		return record(stableOrFallbackDecision(client, descriptor, "session_link_unavailable", RemoteRequestStableErrorDesktopUnavailable))
	}
	if !client.supportsElasticRequestLinks() {
		return record(stableOrFallbackDecision(client, descriptor, "missing_transport_capability", RemoteRequestStableErrorDesktopUnavailable))
	}
	if !client.supportsRemoteEndpoint(descriptor.EndpointID, descriptor.Version, descriptor.RegistryVersion) {
		return record(stableOrFallbackDecision(client, descriptor, "endpoint_registry_mismatch", RemoteRequestStableErrorDesktopUnavailable))
	}
	manager.mu.Lock()
	defer manager.mu.Unlock()
	if linkID := manager.idleLinkLocked(client.machineID, client.currentSessionGeneration(), descriptor); linkID != "" {
		return record(RemoteRequestRouteDecision{Mode: RemoteRequestRouteElasticRequestLink, Reason: "idle_elastic_request_link", LinkID: linkID})
	}
	if manager.activeCountLocked(client.machineID) >= manager.config.MaxPerMachine {
		return record(stableOrFallbackDecision(client, descriptor, "elastic_request_link_capacity", RemoteRequestStableErrorDesktopBusy))
	}
	openCommand, _, err := manager.createGrantLocked(client, descriptor)
	if err != nil {
		stable := RemoteRequestStableErrorDesktopUnavailable
		var linkErr ElasticRequestLinkError
		if errors.As(err, &linkErr) {
			stable = RemoteRequestStableErrorDesktopBusy
		}
		return record(stableOrFallbackDecision(client, descriptor, redactedElasticErrorReason(err), stable))
	}
	return record(RemoteRequestRouteDecision{Mode: RemoteRequestRouteOpenElasticRequestLink, Reason: "open_elastic_request_link", LinkID: openCommand.LinkID, OpenCommand: &openCommand})
}

// isCapabilityMissReason reports whether the given DecideRoute reason codes a
// daemon-side endpoint registry miss so severe that the daemon cannot accept
// this endpoint via ANY transport (neither legacy command, nor typed session
// envelope, nor elastic link). Only "endpoint_registry_mismatch" qualifies:
// the daemon did not advertise this endpoint at the advertised registry
// version, so even the typed RemoteRequestAssignment envelope would be
// rejected.
//
// "missing_transport_capability" is intentionally NOT a capability miss for
// fallback purposes: in the ElasticPreferred branch it means only that the
// daemon lacks the elastic-request-link capability, while it may still
// support the typed session envelope. The canFallBackToSessionDataPlane
// check gates the fallback — if the daemon supports session remote requests
// AND advertises the endpoint, it can carry the data plane; otherwise the
// fallback decision naturally falls through to stable_error.
func isCapabilityMissReason(reason string) bool {
	switch reason {
	case "endpoint_registry_mismatch":
		return true
	default:
		return false
	}
}

func stableOrFallbackDecision(client *clientState, descriptor RemoteEndpointDescriptor, reason string, stable RemoteRequestStableError) RemoteRequestRouteDecision {
	if strings.TrimSpace(descriptor.FallbackCommand) != "" && descriptor.FallbackCommand != commandFallbackNone {
		return RemoteRequestRouteDecision{Mode: RemoteRequestRouteLegacySessionLinkFallback, Reason: reason}
	}
	// Session-link data-plane fallback applies only to ElasticPreferred
	// endpoints with no legacy command: when the elastic transport is
	// operationally unavailable (busy, at capacity, emergency disabled,
	// dial timeout), the typed RemoteRequestAssignment envelope can carry
	// the request via the session link while a new elastic link is being
	// opened in the background. ElasticRequired demands the elastic
	// transport and never falls back; capability/registry misses are
	// upgrade-required conditions and never fall back, because an old
	// daemon cannot accept the typed envelope either.
	if descriptor.LinkPolicy == RemoteEndpointLinkPolicyElasticPreferred &&
		!isCapabilityMissReason(reason) &&
		client.canFallBackToSessionDataPlane(descriptor) {
		return RemoteRequestRouteDecision{Mode: RemoteRequestRouteSessionLink, Reason: reason}
	}
	return RemoteRequestRouteDecision{Mode: RemoteRequestRouteStableError, Reason: reason, StableError: stable}
}
