package exec

import (
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

type workflowCompactionObserverCall struct {
	runID  string
	stepID string
}

type recordingWorkflowCompactionObserver struct {
	completed    []workflowCompactionObserverCall
	unsuccessful []workflowCompactionObserverCall
	canceled     []string
}

func (o *recordingWorkflowCompactionObserver) WorkflowStepCompleted(runID, stepID string) {
	o.completed = append(o.completed, workflowCompactionObserverCall{runID: runID, stepID: stepID})
}

func (o *recordingWorkflowCompactionObserver) WorkflowStepUnsuccessful(runID, stepID string) {
	o.unsuccessful = append(o.unsuccessful, workflowCompactionObserverCall{runID: runID, stepID: stepID})
}

func (o *recordingWorkflowCompactionObserver) WorkflowRunCanceled(runID string) {
	o.canceled = append(o.canceled, runID)
}

func TestWorkflowStepCompleteSuccessAndReplayGrantCompactionCheck(t *testing.T) {
	for _, replayed := range []bool{false, true} {
		t.Run(map[bool]string{false: "completed", true: "same_result_replay"}[replayed], func(t *testing.T) {
			observer := &recordingWorkflowCompactionObserver{}
			server := workflowCompactionObserverServer(t, func(w http.ResponseWriter, request workflowStepTransportRequest) {
				if request.Action != "complete" {
					t.Errorf("workflow step action = %q, want complete", request.Action)
				}
				_ = json.NewEncoder(w).Encode(map[string]any{
					"runId": "run-a", "stepId": "step-a", "status": "completed",
					"runStatus": "waiting", "input": map[string]any{},
					"outputContract": []any{}, "replayed": replayed,
				})
			})

			_, err := (workflowHandler{}).step(t.Context(), actionScopedJSON[workflowStepCommand]{
				Authorized: workflowObserverAuthorization(server, observer),
				Body: workflowStepCommand{
					Action: "complete", RunID: "run-a", StepID: "step-a",
					IdempotencyKey: "complete-step-a",
				},
			})

			require.NoError(t, err)
			require.Equal(t, []workflowCompactionObserverCall{{runID: "run-a", stepID: "step-a"}}, observer.completed)
			require.Empty(t, observer.unsuccessful)
		})
	}
}

func TestWorkflowStepFailSuccessSuppressesItsCompactionBoundary(t *testing.T) {
	observer := &recordingWorkflowCompactionObserver{}
	server := workflowCompactionObserverServer(t, func(w http.ResponseWriter, request workflowStepTransportRequest) {
		if request.Action != "fail" {
			t.Errorf("workflow step action = %q, want fail", request.Action)
		}
		_ = json.NewEncoder(w).Encode(map[string]any{
			"runId": "run-a", "stepId": "step-a", "status": "completed",
			"runStatus": "failed", "input": map[string]any{},
			"outputContract": []any{}, "replayed": false,
		})
	})

	_, err := (workflowHandler{}).step(t.Context(), actionScopedJSON[workflowStepCommand]{
		Authorized: workflowObserverAuthorization(server, observer),
		Body: workflowStepCommand{
			Action: "fail", RunID: "run-a", StepID: "step-a",
			Code: "blocked", IdempotencyKey: "fail-step-a",
		},
	})

	require.NoError(t, err)
	require.Empty(t, observer.completed)
	require.Equal(t, []workflowCompactionObserverCall{{runID: "run-a", stepID: "step-a"}}, observer.unsuccessful)
}

func TestWorkflowStepAuthoritativeTerminalErrorSuppressesCompaction(t *testing.T) {
	for _, test := range []struct {
		name   string
		action string
		code   string
	}{
		{name: "conflicting_complete", action: "complete", code: "step_already_resolved"},
		{name: "externally_terminated_get", action: "get", code: "step_not_waiting"},
	} {
		t.Run(test.name, func(t *testing.T) {
			observer := &recordingWorkflowCompactionObserver{}
			server := workflowCompactionObserverServer(t, func(w http.ResponseWriter, _ workflowStepTransportRequest) {
				w.WriteHeader(http.StatusConflict)
				_, _ = io.WriteString(w, `{"error":"step is terminal","code":"`+test.code+`"}`)
			})
			command := workflowStepCommand{Action: test.action, RunID: "run-a", StepID: "step-a"}
			if test.action == "complete" {
				command.IdempotencyKey = "different-result"
			}

			_, err := (workflowHandler{}).step(t.Context(), actionScopedJSON[workflowStepCommand]{
				Authorized: workflowObserverAuthorization(server, observer),
				Body:       command,
			})

			require.Error(t, err)
			require.Empty(t, observer.completed)
			require.Equal(t, []workflowCompactionObserverCall{{runID: "run-a", stepID: "step-a"}}, observer.unsuccessful)
		})
	}
}

func TestWorkflowStepAmbiguousAndValidationFailuresKeepCompactionGateActive(t *testing.T) {
	t.Run("pause_and_park", func(t *testing.T) {
		for _, action := range []string{"pause", "park"} {
			observer := &recordingWorkflowCompactionObserver{}
			observeWorkflowStepCompactionOutcome(observer, action, "run-a", "step-a", nil)
			require.Empty(t, observer.completed)
			require.Empty(t, observer.unsuccessful)
		}
	})

	t.Run("not_ready", func(t *testing.T) {
		observer := &recordingWorkflowCompactionObserver{}
		observeWorkflowStepCompactionOutcome(
			observer,
			"complete",
			"run-a",
			"step-a",
			&apiclient.APIError{StatusCode: http.StatusConflict, Code: "step_not_ready"},
		)
		require.Empty(t, observer.completed)
		require.Empty(t, observer.unsuccessful)
	})

	t.Run("ambiguous_transport", func(t *testing.T) {
		observer := &recordingWorkflowCompactionObserver{}
		server := workflowCompactionObserverServer(t, func(w http.ResponseWriter, _ workflowStepTransportRequest) {
			http.Error(w, "upstream unavailable", http.StatusBadGateway)
		})

		_, err := (workflowHandler{}).step(t.Context(), actionScopedJSON[workflowStepCommand]{
			Authorized: workflowObserverAuthorization(server, observer),
			Body:       workflowStepCommand{Action: "get", RunID: "run-a", StepID: "step-a"},
		})

		require.Error(t, err)
		require.Empty(t, observer.completed)
		require.Empty(t, observer.unsuccessful)
	})

	t.Run("validation", func(t *testing.T) {
		observer := &recordingWorkflowCompactionObserver{}
		_, err := (workflowHandler{}).step(t.Context(), actionScopedJSON[workflowStepCommand]{
			Authorized: runtimeAuthorization{WorkflowCompaction: observer},
			Body:       workflowStepCommand{Action: "complete"},
		})

		require.Error(t, err)
		require.Empty(t, observer.completed)
		require.Empty(t, observer.unsuccessful)
	})
}

func TestWorkflowRunCancelSuccessSuppressesCompaction(t *testing.T) {
	observer := &recordingWorkflowCompactionObserver{}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if got, want := r.URL.Path, "/api/v1/workspaces/workspace-a/agents/agent-a/runtime/workflow/run"; got != want {
			t.Errorf("workflow run path = %q, want %q", got, want)
		}
		var request workflowCLIRequest[workflowRunRequest]
		if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
			t.Errorf("decode workflow run request: %v", err)
			return
		}
		if request.Input.Action != "cancel" {
			t.Errorf("workflow run action = %q, want cancel", request.Input.Action)
		}
		if request.Input.RunID != "run-a" {
			t.Errorf("workflow run ID = %q, want run-a", request.Input.RunID)
		}
		_, _ = io.WriteString(w, `{
			"run":{
				"id":"run-a","planId":"plan-a","planVersionId":"version-a",
				"status":"canceled","queuedAt":"2026-08-18T00:00:00Z",
				"canceledAt":"2026-08-18T00:01:00Z"
			},
			"replayed":false
		}`)
	}))
	t.Cleanup(server.Close)

	_, err := (workflowHandler{}).run(t.Context(), actionScopedJSON[workflowRunRequest]{
		Authorized: workflowObserverAuthorization(server, observer),
		Body:       workflowRunRequest{Action: "cancel", RunID: "run-a", Reason: "no longer needed"},
	})

	require.NoError(t, err)
	require.Equal(t, []string{"run-a"}, observer.canceled)
}

func workflowCompactionObserverServer(
	t *testing.T,
	respond func(http.ResponseWriter, workflowStepTransportRequest),
) *httptest.Server {
	t.Helper()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if got, want := r.URL.Path, "/api/v1/workspaces/workspace-a/agents/agent-a/runtime/workflow/step"; got != want {
			t.Errorf("workflow step path = %q, want %q", got, want)
		}
		var request workflowStepTransportRequest
		if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
			t.Errorf("decode workflow step request: %v", err)
			return
		}
		respond(w, request)
	}))
	t.Cleanup(server.Close)
	return server
}

func workflowObserverAuthorization(
	server *httptest.Server,
	observer *recordingWorkflowCompactionObserver,
) runtimeAuthorization {
	return runtimeAuthorization{
		Server: &apiclient.Client{
			ServerURL: server.URL, RouteWorkspaceID: "workspace-a",
			AgentID: "agent-a", LocalAgentID: "agent-a",
			AuthToken: "token", HTTPClient: server.Client(),
		},
		WorkflowCompaction: observer,
	}
}
