package daemon

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

// TestReviewSnapshotFingerprintFallbackToSessionLinkDataPlane pins the
// reviewer3/龙恒 contract for ElasticPreferred endpoints that have no legacy
// Session Link command (FallbackCommand=none): when no idle Elastic Request
// Link is available and the per-machine capacity is hit, the route decision
// MUST fall back to the typed RemoteRequestAssignment envelope over the
// session link (Mode=session_link), NOT return a stable_error.
//
// Without this fallback, snapshot/fingerprint would surface as HTTP 500
// (snapshot) / 501 (fingerprint) the moment ERL hit capacity or was
// emergency-disabled — even though the daemon is perfectly capable of
// carrying the same payload via the typed session envelope.
func TestReviewSnapshotFingerprintFallbackToSessionLinkDataPlane(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 7, 12, 11, 0, 0, 0, time.UTC)
	// MaxPerMachine=1 + an existing open link saturates capacity, so
	// DecideRoute hits "elastic_request_link_capacity" on the second call.
	manager := newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{MaxPerMachine: 1})
	client := newElasticReadyClient()

	snapshotDescriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewSnapshot, 1)
	require.True(t, ok)
	require.Equal(t, RemoteEndpointLinkPolicyElasticPreferred, snapshotDescriptor.LinkPolicy)
	require.Equal(t, commandFallbackNone, snapshotDescriptor.FallbackCommand)

	fingerprintDescriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewFingerprint, 1)
	require.True(t, ok)
	require.Equal(t, RemoteEndpointLinkPolicyElasticPreferred, fingerprintDescriptor.LinkPolicy)
	require.Equal(t, commandFallbackNone, fingerprintDescriptor.FallbackCommand)

	// First call opens an ERL (state transitions to "open pending" → counts
	// against MaxPerMachine). Capacity is now saturated.
	first := manager.DecideRoute(client, snapshotDescriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteOpenElasticRequestLink, first.Mode,
		"first call should open an ERL since none exists yet")

	// Second call hits capacity. The legacy-fallback branch is unavailable
	// (FallbackCommand=none), but the daemon advertises the endpoint and
	// supports session remote requests, so the decision MUST fall back to
	// the typed session-link data plane — NOT stable_error.
	second := manager.DecideRoute(client, snapshotDescriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteSessionLink, second.Mode,
		"snapshot at ERL capacity MUST fall back to session-link data plane, not stable_error")
	require.Equal(t, "elastic_request_link_capacity", second.Reason)

	// Fingerprint behaves identically (separate endpoint, same policy).
	fingerprint := manager.DecideRoute(client, fingerprintDescriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteSessionLink, fingerprint.Mode,
		"fingerprint at ERL capacity MUST fall back to session-link data plane, not stable_error")
	require.Equal(t, "elastic_request_link_capacity", fingerprint.Reason)
}

// TestReviewSnapshotEmergencyDisabledFallsBackToSessionLinkDataPlane pins
// the same fallback when the ERL manager is emergency-disabled. The data
// plane must still flow via session link for ElasticPreferred endpoints with
// no legacy command.
func TestReviewSnapshotEmergencyDisabledFallsBackToSessionLinkDataPlane(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 7, 12, 11, 0, 0, 0, time.UTC)
	manager := newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{EmergencyDisabled: true})
	client := newElasticReadyClient()

	snapshotDescriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewSnapshot, 1)
	require.True(t, ok)

	decision := manager.DecideRoute(client, snapshotDescriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteSessionLink, decision.Mode,
		"snapshot with ERL emergency-disabled MUST fall back to session-link data plane")
	require.Equal(t, "elastic_request_disabled", decision.Reason)
}

// TestReviewSnapshotCapabilityMissDoesNotFallBackToSessionDataPlane pins
// the symmetric invariant: an old daemon that does not advertise the
// snapshot endpoint cannot accept the typed envelope either, so the
// decision MUST surface a stable_error (which the API layer maps to a
// 501/upgrade) — NOT mask it as a session-link fallback.
func TestReviewSnapshotCapabilityMissDoesNotFallBackToSessionDataPlane(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 7, 12, 11, 0, 0, 0, time.UTC)
	manager := newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{MaxPerMachine: 3})
	client := newElasticReadyClient()

	snapshotDescriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewSnapshot, 1)
	require.True(t, ok)

	// Strip ONLY the snapshot endpoint from the client's advertised list.
	// The client still supports session remote requests and the endpoints
	// capability itself; snapshot is the only thing missing.
	endpoints := defaultRemoteEndpointRefs()
	filtered := make([]daemonTransportEndpointRef, 0, len(endpoints))
	for _, ref := range endpoints {
		if ref.EndpointID == remoteEndpointReviewSnapshot {
			continue
		}
		filtered = append(filtered, ref)
	}
	client.setDaemonTransportReady(daemonTransportReady{
		SchemaVersion:           defaultElasticRequestLinkProtocolVersion,
		SessionGeneration:       "gen_1",
		EndpointRegistryVersion: defaultEndpointRegistryVersion,
		SupportedEndpoints:      filtered,
	})

	decision := manager.DecideRoute(client, snapshotDescriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteStableError, decision.Mode,
		"snapshot endpoint capability miss MUST NOT fall back to session data plane — old daemon cannot accept the typed envelope")
	require.Equal(t, "endpoint_registry_mismatch", decision.Reason)
	require.Equal(t, RemoteRequestStableErrorDesktopUnavailable, decision.StableError)
}

// TestReviewSnapshotMissingElasticCapabilityFallsBackToSessionDataPlane
// pins the second half of the ElasticPreferred fallback contract: a daemon
// that supports the typed session endpoint (advertises review.snapshot in
// its endpoint registry) but does NOT advertise the elastic-request-link
// transport capability must still receive traffic via the typed session
// envelope. `missing_transport_capability` is NOT a hard capability miss —
// the daemon can carry the payload over the session data plane — so the
// decision MUST be SessionLink, not StableError.
func TestReviewSnapshotMissingElasticCapabilityFallsBackToSessionDataPlane(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 7, 12, 11, 0, 0, 0, time.UTC)
	manager := newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{MaxPerMachine: 3})

	// Client supports ONLY the session transport — no elastic capability.
	// It still advertises the review.snapshot endpoint, so the typed
	// envelope dispatches cleanly on the session link.
	client := &clientState{
		machineID:                 "mac_1",
		workspaceID:               "wsp_1",
		capabilities:              make(map[string]struct{}),
		supportedEndpointVersions: make(map[string]map[int]struct{}),
	}
	client.setCapabilities([]string{daemonTransportCapabilityV1, daemonEndpointsCapabilityV1})
	client.setDaemonTransportReady(daemonTransportReady{
		SchemaVersion:           defaultElasticRequestLinkProtocolVersion,
		SessionGeneration:       "gen_1",
		EndpointRegistryVersion: defaultEndpointRegistryVersion,
		SupportedEndpoints:      defaultRemoteEndpointRefs(),
	})

	snapshotDescriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewSnapshot, 1)
	require.True(t, ok)
	require.Equal(t, RemoteEndpointLinkPolicyElasticPreferred, snapshotDescriptor.LinkPolicy)
	require.Equal(t, commandFallbackNone, snapshotDescriptor.FallbackCommand)

	decision := manager.DecideRoute(client, snapshotDescriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteSessionLink, decision.Mode,
		"daemon with session transport + endpoint but no ERL capability MUST fall back to session data plane, not stable_error")
	require.Equal(t, "missing_transport_capability", decision.Reason)
}

// TestReviewSnapshotElasticRequiredDoesNotFallBackToSessionDataPlane pins
// the policy boundary: ElasticRequired endpoints demand the elastic
// transport. Even when the client supports the session data plane,
// operational ERL failures surface as stable_error so the caller knows the
// elastic transport is required and currently unavailable.
func TestReviewSnapshotElasticRequiredDoesNotFallBackToSessionDataPlane(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 7, 12, 11, 0, 0, 0, time.UTC)
	manager := newTestElasticRequestLinkManager(t, &now, ElasticRequestLinkManagerConfig{MaxPerMachine: 1})
	client := newElasticReadyClient()

	descriptor, ok := defaultRemoteEndpointRegistry().Lookup(remoteEndpointReviewSnapshot, 1)
	require.True(t, ok)
	descriptor.LinkPolicy = RemoteEndpointLinkPolicyElasticRequired
	descriptor.FallbackCommand = commandFallbackNone

	// Saturate capacity so the next call hits the capacity branch.
	first := manager.DecideRoute(client, descriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteOpenElasticRequestLink, first.Mode)

	second := manager.DecideRoute(client, descriptor, RemoteRequestRouteOptions{})
	require.Equal(t, RemoteRequestRouteStableError, second.Mode,
		"ElasticRequired endpoints MUST NOT fall back to session data plane")
	require.Equal(t, RemoteRequestStableErrorDesktopBusy, second.StableError)
}
