package cmd

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agentview"
	agentteamexec "github.com/tidbcloud/AgentTeam/desktop/internal/api/exec"
	"github.com/tidbcloud/AgentTeam/desktop/internal/token"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

type realExecAgentReader struct{}

func (realExecAgentReader) ListAgents(context.Context) ([]agentview.Snapshot, error) {
	return []agentview.Snapshot{{
		ID: "agt_safe", Name: "Safe Agent", Runtime: "codex", Status: "active",
		BaseURL:          "https://user:password@example.test/?token=secret",
		Endpoint:         "http://127.0.0.1:7777/internal?token=secret",
		Headers:          agentview.Headers{"Authorization": "Bearer secret", "X-API-Key": "api-secret", "X-Provider": "custom-secret"},
		APIKeyConfigured: true,
		Policy:           &agentview.Policy{AllowedPaths: []string{"/home/private/repo"}},
	}}, nil
}

func (reader realExecAgentReader) GetAgent(ctx context.Context, id string) (agentview.Snapshot, bool, error) {
	items, err := reader.ListAgents(ctx)
	if err != nil || len(items) == 0 || items[0].ID != id {
		return agentview.Snapshot{}, false, err
	}
	return items[0], true, nil
}

// TestExecAgentRouteTransportProjectionAndRetry exercises the registered
// daemon route, real LocalClient/Cobra JSON, and the Agent Runtime transport.
// This intentionally uses a transport-only HTTP harness: it is not evidence of
// durable persistence. Production receipt persistence is covered by
// TestExecNonWorkflowMutationsUseProductionReceiptStores.
func TestExecAgentRouteTransportProjectionAndRetry(t *testing.T) {
	gin.SetMode(gin.TestMode)
	type receipt struct {
		path string
		body string
		data []byte
	}
	var mu sync.Mutex
	receipts := map[string]receipt{}
	applied := 0
	transient := true
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.URL.Path, "/managed-agents/") {
			w.WriteHeader(http.StatusNotFound)
			return
		}
		body, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		key := r.Header.Get("Idempotency-Key")
		require.Equal(t, key, r.Header.Get("X-Request-ID"))
		mu.Lock()
		defer mu.Unlock()
		if key == "req-transient" && transient {
			transient = false
			w.WriteHeader(http.StatusServiceUnavailable)
			_, _ = io.WriteString(w, `{"error":{"code":"upstream_failure","message":"temporary"}}`)
			return
		}
		if prior, ok := receipts[key]; ok {
			if prior.path != r.URL.Path || prior.body != string(body) {
				w.WriteHeader(http.StatusConflict)
				_, _ = io.WriteString(w, `{"error":{"code":"conflict","message":"replay selector payload conflict"}}`)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			_, _ = w.Write(prior.data)
			return
		}
		applied++
		data := []byte(`{"status":"started"}`)
		receipts[key] = receipt{path: r.URL.Path, body: string(body), data: data}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write(data)
	}))
	defer upstream.Close()

	keys, err := token.NewKeyManager(t.TempDir() + "/config.yaml")
	require.NoError(t, err)
	authorizations := agentteamexec.NewRuntimeAuthorizationStore(keys)
	lease, err := authorizations.Create(agentteamexec.RuntimeAuthorizationConfig{
		ServerID: "server-1", AgentID: "agent-1", SessionID: "session-1", RunID: "run-1",
		WorkDir: t.TempDir(), Server: &apiclient.Client{
			ServerURL: upstream.URL, AgentID: "agent-1", AuthToken: "upstream-token",
			RouteWorkspaceID: "workspace-1", HTTPClient: upstream.Client(),
		}, TokenTTL: time.Hour,
	})
	require.NoError(t, err)
	router := gin.New()
	agentteamexec.RegisterRoutes(router.Group("/api/internal/exec"), agentteamexec.RouteDependencies{
		Authorizations: authorizations, Agents: realExecAgentReader{},
	})
	daemon := httptest.NewServer(router)
	defer daemon.Close()
	setExecCommandTokenFile(t, daemon.URL+"/api/internal/exec/servers/server-1/agents/agent-1", lease.Token)

	stdout, stderr, err := executeCobra(newExecCommand(), "agent", "list", `{"limit":1}`)
	require.NoError(t, err)
	require.Empty(t, stderr)
	require.NotContains(t, stdout, "secret")
	require.NotContains(t, stdout, "private/repo")

	stdout, stderr, err = executeCobra(newExecCommand(), "agent", "get", `{"agentId":"agt_safe"}`)
	require.NoError(t, err)
	require.Empty(t, stderr)
	require.True(t, json.Valid([]byte(strings.TrimSpace(stdout))))
	require.NotContains(t, stdout, "secret")
	require.NotContains(t, stdout, "private/repo")
	require.NotContains(t, stdout, "baseUrl")
	require.NotContains(t, stdout, "endpoint")
	require.Contains(t, stdout, "configuredHeaderNames")
	require.Contains(t, stdout, "apiKeyConfigured")

	_, stderr, err = executeCobra(newExecCommand(), "agent", "start", `{"requestId":"req-start","agentId":"agt_safe"}`)
	require.NoError(t, err, stderr)
	_, stderr, err = executeCobra(newExecCommand(), "agent", "start", `{"requestId":"req-start","agentId":"agt_safe"}`)
	require.NoError(t, err, stderr)
	mu.Lock()
	require.Equal(t, 1, applied, "exact replay must apply the upstream effect once")
	mu.Unlock()

	_, stderr, err = executeCobra(newExecCommand(), "agent", "stop", `{"requestId":"req-start","agentId":"agt_safe"}`)
	require.Error(t, err)
	require.Contains(t, stderr, `"code":"conflict"`)
	exit, ok := ExecExitCode(err)
	require.True(t, ok)
	require.Equal(t, 1, exit)

	_, stderr, err = executeCobra(newExecCommand(), "agent", "start", `{"requestId":"req-transient","agentId":"agt_safe"}`)
	require.NoError(t, err, stderr)
	mu.Lock()
	require.Equal(t, 2, applied, "one transient replay retry must apply once")
	mu.Unlock()

	req, err := http.NewRequest(http.MethodPost, daemon.URL+"/api/internal/exec/servers/server-1/agents/agent-1/agent/start", strings.NewReader(`{"requestId":"req-body","agentId":"agt_safe"}`))
	require.NoError(t, err)
	req.Header.Set("Authorization", "Bearer "+lease.Token)
	req.Header.Set("Idempotency-Key", "req-header")
	req.Header.Set("X-Request-ID", "req-header")
	response, err := daemon.Client().Do(req)
	require.NoError(t, err)
	defer response.Body.Close()
	require.Equal(t, http.StatusConflict, response.StatusCode)
	mu.Lock()
	require.Equal(t, 2, applied, "header/body disagreement must fail before the upstream effect")
	mu.Unlock()
}
