package webagent

import (
	"errors"
	"net/http"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
)

// TestReviewFingerprintReturnsWorkspaceTargetPathUpgradeRequiredAs409
// pins the HTTP mapping codex3's reviewer gate requires: when the daemon
// Hub signals that the connected daemon cannot browse a target path
// (workspace:target-path capability miss), the webagent Fingerprint
// handler MUST return HTTP 409 with the upgrade message — NOT 500 or
// 503. Without this mapping the frontend would treat the condition as a
// transient 500 and retry-storm instead of prompting a daemon upgrade.
func TestReviewFingerprintReturnsWorkspaceTargetPathUpgradeRequiredAs409(t *testing.T) {
	store := &fakeStore{review: &agent.ReviewContext{ID: "rcx_1", MachineID: "mach_1", SourceAgentID: "agt_1", RootPath: "/repo"}}
	runtime := &fakeRuntime{fingerprintErr: runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired}
	handler := &ReviewHandler{reviewStore: store, runtime: runtime}

	_, err := handler.Fingerprint(reviewFingerprintRequest(t))
	var httpErr api.HTTPError
	require.True(t, errors.As(err, &httpErr))
	require.Equal(t, http.StatusConflict, httpErr.Status,
		"target-path capability miss MUST return as 409 so the frontend prompts for a daemon upgrade; got %v", err)
	require.Equal(t, runtimecontrol.WorkspaceTargetPathDaemonUpgradeMessage, httpErr.Error())
}

// TestReviewSnapshotReturnsWorkspaceTargetPathUpgradeRequiredAs409 is the
// Snapshot-handler counterpart of the fingerprint test above. Both review
// returns (Thread + Explore) MUST classify target-path upgrade
// identically so the frontend can show the same upgrade prompt
// regardless of which endpoint issued the request.
func TestReviewSnapshotReturnsWorkspaceTargetPathUpgradeRequiredAs409(t *testing.T) {
	store := &fakeStore{review: &agent.ReviewContext{ID: "rcx_1", MachineID: "mach_1", SourceAgentID: "agt_1", RootPath: "/repo"}}
	// snapshot MUST be non-nil; otherwise GetAgentReviewSnapshot short-circuits
	// to ErrDaemonReviewSnapshotUnavailable and never returns our injected
	// snapshotErr.
	runtime := &fakeRuntime{snapshot: &agent.AgentReviewSnapshot{}, snapshotErr: runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired}
	handler := &ReviewHandler{reviewStore: store, runtime: runtime}

	_, err := handler.Snapshot(reviewSnapshotRequest(t))
	var httpErr api.HTTPError
	require.True(t, errors.As(err, &httpErr))
	require.Equal(t, http.StatusConflict, httpErr.Status,
		"target-path capability miss MUST return as 409 on the Snapshot endpoint too; got %v", err)
	require.Equal(t, runtimecontrol.WorkspaceTargetPathDaemonUpgradeMessage, httpErr.Error())
}

// TestReviewFingerprintReturnsStableBusyAs503 pins the mapping for the
// stable "desktop_busy" error string emitted by the remote-endpoint
// routing layer when an ElasticPreferred endpoint hits per-machine ERL
// capacity and the session-link data-plane fallback is unavailable. The
// frontend MUST see HTTP 503 (not 500) so it can retry with backoff
// instead of treating the response as a fatal server error.
func TestReviewFingerprintReturnsStableBusyAs503(t *testing.T) {
	store := &fakeStore{review: &agent.ReviewContext{ID: "rcx_1", MachineID: "mach_1", SourceAgentID: "agt_1", RootPath: "/repo"}}
	runtime := &fakeRuntime{fingerprintErr: errors.New("desktop_busy")}
	handler := &ReviewHandler{reviewStore: store, runtime: runtime}

	_, err := handler.Fingerprint(reviewFingerprintRequest(t))
	var httpErr api.HTTPError
	require.True(t, errors.As(err, &httpErr))
	require.Equal(t, http.StatusServiceUnavailable, httpErr.Status,
		"stable desktop_busy MUST return as 503 so clients retry; got %v", err)
}

// TestReviewSnapshotReturnsStableUnavailableAs503 pins the mapping for
// the stable "desktop_unavailable" error string. Snapshot has a
// composite legacy fallback at the webagent layer that triggers on the
// TYPED ErrDaemonReviewSnapshotUnavailable sentinel; the opaque
// "desktop_unavailable" stable error bypasses that fallback and reaches
// reviewRuntimeError directly, where it MUST be classified as 503 (not
// 500) so clients can retry.
func TestReviewSnapshotReturnsStableUnavailableAs503(t *testing.T) {
	store := &fakeStore{review: &agent.ReviewContext{ID: "rcx_1", MachineID: "mach_1", SourceAgentID: "agt_1", RootPath: "/repo"}}
	// snapshot MUST be non-nil; otherwise GetAgentReviewSnapshot short-circuits
	// to ErrDaemonReviewSnapshotUnavailable (typed sentinel) and triggers the
	// composite legacy fallback, never reaching the stable-error path.
	runtime := &fakeRuntime{snapshot: &agent.AgentReviewSnapshot{}, snapshotErr: errors.New("desktop_unavailable")}
	handler := &ReviewHandler{reviewStore: store, runtime: runtime}

	_, err := handler.Snapshot(reviewSnapshotRequest(t))
	var httpErr api.HTTPError
	require.True(t, errors.As(err, &httpErr))
	require.Equal(t, http.StatusServiceUnavailable, httpErr.Status,
		"stable desktop_unavailable MUST return as 503; got %v", err)
}
