package run

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

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	runstore "github.com/tidbcloud/AgentTeam/internal/store/workflow/run"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

func TestReadHandlersPassWorkspaceFiltersAndCursors(t *testing.T) {
	now := time.Date(2026, 7, 21, 12, 0, 0, 0, time.UTC)
	run := apiRunFixture(now)
	fake := &fakeService{
		run:     run,
		runPage: RunPage{Items: []runstore.Run{run}, NextCursor: "run-next"},
		activationPage: ActivationPage{Items: []runstore.Activation{{
			ID: "wra_6b86b273ff34fce19d6b804eff5a3f57", RunID: run.ID, LogicalKey: "a1", NodeID: "send", Opcode: "send_message",
			Status: runstore.ActivationStatusWaiting, InputJSON: json.RawMessage(`{}`),
			ScopeContextJSON: json.RawMessage(`{}`), CreatedAt: now, UpdatedAt: now,
		}}, NextCursor: "activation-next"},
		effectPage: EffectPage{Items: []runstore.Effect{{
			ID: "wfx_baa86a9e8b16589a778ee2cfce0bef62", RunID: run.ID, ActivationID: "wra_6b86b273ff34fce19d6b804eff5a3f57", Kind: "send_message",
			Status: runstore.EffectStatusAwaiting, CreatedAt: now, UpdatedAt: now,
		}}, NextCursor: "effect-next"},
		eventPage: EventPage{Items: []runstore.Event{{
			ID: "wre_1", RunID: run.ID, Seq: 1, Type: "run_accepted", PayloadJSON: json.RawMessage(`{}`), CreatedAt: now,
		}}, NextCursor: "event-next"},
	}
	handler := NewHandler(fake)

	listResponse, err := handler.List(humanRunRequest(t, http.MethodGet, "/workflow/runs?status=waiting&limit=25&cursor=cur", nil))
	require.NoError(t, err)
	require.Equal(t, &ListRunsInput{WorkspaceID: "wsp_1", Status: "waiting", Limit: 25, Cursor: "cur"}, fake.listRunsInput)
	require.Equal(t, "run-next", listResponse.Body.(dto.WorkflowRunListResponse).NextCursor)
	require.Nil(t, listResponse.Body.(dto.WorkflowRunListResponse).Items[0].State)

	getResponse, err := handler.Get(humanRunRequest(t, http.MethodGet, "/workflow/runs/wri_1", map[string]string{"runId": run.ID}))
	require.NoError(t, err)
	require.Equal(t, "wsp_1", fake.getWorkspaceID)
	require.Equal(t, run.ID, fake.getRunID)
	require.JSONEq(t, `{"status":"waiting"}`, string(getResponse.Body.(dto.WorkflowRunResponse).State))

	activationResponse, err := handler.ListActivations(humanRunRequest(t, http.MethodGet,
		"/workflow/runs/wri_1/activations?status=waiting&limit=9&cursor=acur", map[string]string{"runId": run.ID}))
	require.NoError(t, err)
	require.Equal(t, &ListChildrenInput{WorkspaceID: "wsp_1", RunID: run.ID, Status: "waiting", Limit: 9, Cursor: "acur"}, fake.activationInput)
	require.Equal(t, "activation-next", activationResponse.Body.(dto.WorkflowRunActivationListResponse).NextCursor)

	_, err = handler.ListEffects(humanRunRequest(t, http.MethodGet,
		"/workflow/runs/wri_1/effects?status=awaiting&limit=8&cursor=ecur", map[string]string{"runId": run.ID}))
	require.NoError(t, err)
	require.Equal(t, &ListChildrenInput{WorkspaceID: "wsp_1", RunID: run.ID, Status: "awaiting", Limit: 8, Cursor: "ecur"}, fake.effectInput)

	_, err = handler.ListEvents(humanRunRequest(t, http.MethodGet,
		"/workflow/runs/wri_1/events?limit=7&cursor=tcur", map[string]string{"runId": run.ID}))
	require.NoError(t, err)
	require.Equal(t, &ListEventsInput{WorkspaceID: "wsp_1", RunID: run.ID, Limit: 7, Cursor: "tcur"}, fake.eventInput)
}

func TestCancelDerivesHumanActorAndNeverAcceptsIdentityFromBody(t *testing.T) {
	now := time.Now().UTC()
	run := apiRunFixture(now)
	run.Status = runstore.RunStatusCanceled
	event := runstore.Event{ID: "wre_2", RunID: run.ID, Seq: 2, Type: "run_canceled", PayloadJSON: json.RawMessage(`{}`), CreatedAt: now}
	fake := &fakeService{cancelResult: CancelResult{Run: run, Event: &event}}
	handler := NewHandler(fake)
	request := humanRunRequest(t, http.MethodPost, "/workflow/runs/wri_1/cancel", map[string]string{"runId": run.ID})

	response, err := handler.Cancel(api.WorkspaceScopedJSON[dto.WorkflowRunCancelActionRequest]{
		WorkspaceScopedRequest: request,
		Body:                   dto.WorkflowRunCancelActionRequest{Reason: "no longer needed"},
	})
	require.NoError(t, err)
	require.Equal(t, &CancelInput{
		WorkspaceID: "wsp_1", RunID: run.ID, Reason: "no longer needed",
		Actor: Actor{Type: "human", ID: "usr_00000000000001", MemberID: "000000000001"},
	}, fake.cancelInput)
	body := response.Body.(dto.WorkflowRunCancelResponse)
	require.Equal(t, runstore.RunStatusCanceled, body.Run.Status)
	require.NotNil(t, body.Event)
	require.False(t, body.Replayed)
}

func TestHandlersRejectMissingRunAndInvalidLimitBeforeService(t *testing.T) {
	fake := &fakeService{}
	handler := NewHandler(fake)
	_, err := handler.Get(humanRunRequest(t, http.MethodGet, "/workflow/runs/", nil))
	requireRunHTTPError(t, err, http.StatusBadRequest, "run_invalid_request")
	require.Empty(t, fake.getRunID)

	_, err = handler.List(humanRunRequest(t, http.MethodGet, "/workflow/runs?limit=101", nil))
	requireRunHTTPError(t, err, http.StatusBadRequest, "run_invalid_request")
	require.Nil(t, fake.listRunsInput)
}

func humanRunRequest(t *testing.T, method, path string, params map[string]string) api.WorkspaceScopedRequest {
	t.Helper()
	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	ctx.Request = httptest.NewRequest(method, path, http.NoBody)
	for key, value := range params {
		ctx.Params = append(ctx.Params, gin.Param{Key: key, Value: value})
	}
	return api.WorkspaceScopedRequest{
		AuthenticatedRequest: api.AuthenticatedRequest{Gin: ctx, User: identity.AuthenticatedUser{User: identity.User{ID: "usr_00000000000001"}}},
		Workspace:            workspace.Summary{WorkspaceID: "wsp_1", MemberID: "000000000001", MemberUserID: "usr_00000000000001", WorkspaceRole: "owner"},
	}
}

func apiRunFixture(now time.Time) runstore.Run {
	return runstore.Run{
		ID: "wri_1", WorkspaceID: "wsp_1", InvocationID: "wiv_1",
		PlanID: "pln_1", PlanVersionID: "plv_1",
		Status: runstore.RunStatusWaiting, InputJSON: json.RawMessage(`{"word":"画蛇添足"}`),
		StateJSON: json.RawMessage(`{"status":"waiting"}`), QueuedAt: now, Revision: 3,
		CreatedAt: now, UpdatedAt: now,
	}
}

func requireRunHTTPError(t *testing.T, err error, status int, code string) {
	t.Helper()
	response, ok := classifyError(err)
	require.True(t, ok)
	require.Equal(t, status, response.Status)
	require.Equal(t, code, response.Code)
	require.Equal(t, "run", response.Stage)
}
