package daemon

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
)

// TestReviewFingerprintCapabilityGateRunsBeforeDelegation pins reviewer3's
// acceptance contract for the target-path capability gate on the fingerprint
// RPC: when targetPath is non-empty AND the client lacks
// workspaceTargetPathCapability, GetAgentReviewFingerprint MUST return
// ErrDaemonWorkspaceTargetPathUpgradeRequired without ever dispatching to the
// fingerprint delegation (which itself short-circuits to
// ErrDaemonReviewFingerprintUnavailable when elasticRequestLinks is nil).
//
// The priority proof is in the ERROR IDENTITY: if the gate had not run first,
// the delegation would return ErrDaemonReviewFingerprintUnavailable (line 131
// of remote_review_endpoints.go: hub.elasticRequestLinks == nil branch). We
// assert the UPGRADE error, not the UNAVAILABLE error — proving the gate
// fired first and the delegation never ran, so zero snapshot/legacy RPC.
func TestReviewFingerprintCapabilityGateRunsBeforeDelegation(t *testing.T) {
	t.Parallel()

	hub := &Hub{
		clients: map[string]*clientState{
			// Client has NO capabilities at all — no workspaceTargetPathCapability,
			// no daemonEndpointsCapabilityV1. Both the gate and the delegation
			// would fail; which one fires first is what this test pins.
			"mac_test": {
				machineID:    "mac_test",
				capabilities: make(map[string]struct{}),
			},
		},
		// elasticRequestLinks is nil on purpose. If the gate fails to fire,
		// hub.reviewFingerprint short-circuits at remote_review_endpoints.go:131
		// and returns ErrDaemonReviewFingerprintUnavailable. We assert below
		// that the error is NOT that — it's the upgrade-required error.
	}

	_, err := hub.GetAgentReviewFingerprint(
		context.Background(),
		"mac_test", "agt_test",
		"/tmp/review", // non-empty targetPath triggers the gate
		"main", "feature", "direct",
	)
	require.ErrorIs(t, err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired,
		"capability gate MUST fire first and return the upgrade-required error")
	require.False(t, errors.Is(err, runtimecontrol.ErrDaemonReviewFingerprintUnavailable),
		"delegation MUST NOT have run — fingerprint-unavailable error would mean the gate was bypassed")
}

// TestReviewFingerprintCapabilityGateNoOpsForEmptyTargetPath pins the inverse
// direction: when targetPath is empty, the gate is a no-op and the request
// flows through to the delegation. Combined with the test above, this proves
// the gate is keyed on the EXACT-empty check (targetPath == ""), not on a
// TrimSpace — otherwise a whitespace-only targetPath would slip past.
func TestReviewFingerprintCapabilityGateNoOpsForEmptyTargetPath(t *testing.T) {
	t.Parallel()

	hub := &Hub{
		clients: map[string]*clientState{
			"mac_test": {
				machineID:    "mac_test",
				capabilities: make(map[string]struct{}),
			},
		},
	}

	_, err := hub.GetAgentReviewFingerprint(
		context.Background(),
		"mac_test", "agt_test",
		"", // empty targetPath: gate is a no-op
		"main", "feature", "direct",
	)
	// Delegation runs; with nil elasticRequestLinks it returns the fingerprint
	// sentinel at remote_review_endpoints.go:131.
	require.ErrorIs(t, err, runtimecontrol.ErrDaemonReviewFingerprintUnavailable,
		"empty targetPath MUST bypass the capability gate and flow to the delegation")
	require.False(t, errors.Is(err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired),
		"empty targetPath MUST NOT trigger the upgrade-required error")
}

// TestReviewFingerprintGateDoesNotShadowFingerprintMissWhenCapabilityPresent
// pins the third leg of the priority contract: when the client HAS
// workspaceTargetPathCapability (so the gate passes) but does NOT have
// daemonEndpointsCapabilityV1 (so the fingerprint endpoint is unsupported),
// the delegation runs and returns ErrDaemonReviewFingerprintUnavailable. This
// proves the gate does NOT swallow the fingerprint miss when capability is
// present — the two errors are acked independently and the frontend can
// distinguish "upgrade for target-path" from "upgrade for fingerprint".
func TestReviewFingerprintGateDoesNotShadowFingerprintMissWhenCapabilityPresent(t *testing.T) {
	t.Parallel()

	hub := &Hub{
		clients: map[string]*clientState{
			"mac_test": {
				machineID: "mac_test",
				capabilities: map[string]struct{}{
					workspaceTargetPathCapability: {},
					// daemonEndpointsCapabilityV1 intentionally absent → the
					// fingerprint endpoint check fails inside the delegation.
				},
			},
		},
	}

	_, err := hub.GetAgentReviewFingerprint(
		context.Background(),
		"mac_test", "agt_test",
		"/tmp/review", // non-empty targetPath, but client HAS the capability
		"main", "feature", "direct",
	)
	require.ErrorIs(t, err, runtimecontrol.ErrDaemonReviewFingerprintUnavailable,
		"with target-path capability present, fingerprint miss MUST surface from delegation")
	require.False(t, errors.Is(err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired),
		"with target-path capability present, upgrade-required MUST NOT surface")
}

// TestReviewMethodsAllCarryTargetPathCapabilityGate verifies the gate exists
// on ALL five Review Hub methods (Summary/Snapshot/Fingerprint/Files/Diff),
// not just Fingerprint. This pins the symmetric coverage reviewer3 required:
// no Review RPC can bypass the capability check by routing through a sibling
// method. Each method is exercised with the same fixture (non-empty
// targetPath, no capabilities) and asserted to surface the same
// upgrade-required error.
func TestReviewMethodsAllCarryTargetPathCapabilityGate(t *testing.T) {
	t.Parallel()

	const (
		machineID  = "mac_test"
		agentID    = "agt_test"
		targetPath = "/tmp/review"
	)
	ctx := context.Background()

	hub := &Hub{
		clients: map[string]*clientState{
			machineID: {
				machineID:    machineID,
				capabilities: make(map[string]struct{}),
			},
		},
	}

	t.Run("summary", func(t *testing.T) {
		t.Parallel()
		_, err := hub.GetAgentReviewSummary(ctx, machineID, agentID, targetPath, "main", "feature", "direct")
		require.ErrorIs(t, err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired)
	})
	t.Run("snapshot", func(t *testing.T) {
		t.Parallel()
		_, err := hub.GetAgentReviewSnapshot(ctx, machineID, agentID, targetPath, "main", "feature", "direct")
		require.ErrorIs(t, err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired)
	})
	t.Run("fingerprint", func(t *testing.T) {
		t.Parallel()
		_, err := hub.GetAgentReviewFingerprint(ctx, machineID, agentID, targetPath, "main", "feature", "direct")
		require.ErrorIs(t, err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired)
	})
	t.Run("files", func(t *testing.T) {
		t.Parallel()
		_, err := hub.ListAgentReviewFiles(ctx, machineID, agentID, targetPath, "branch", "main", "feature", "direct")
		require.ErrorIs(t, err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired)
	})
	t.Run("diff", func(t *testing.T) {
		t.Parallel()
		_, err := hub.GetAgentReviewDiff(ctx, machineID, agentID, targetPath, "branch", "main.go", "main", "feature", "direct")
		require.ErrorIs(t, err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired)
	})
}
