package onboarding

import (
	"context"
	"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/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	storeonboarding "github.com/tidbcloud/AgentTeam/internal/store/onboarding"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

func TestBootstrapReturnsEligibleTutorialStateAndReplyEvidence(t *testing.T) {
	state := activeOnboardingState()
	state.Progress = storeonboarding.Progress{ReplyMessageID: "msg_reply", ReplySeq: 42, ReplyScopeID: "chn_reply"}
	store := &fakeOnboardingStore{state: state, ensureEligible: true}
	handler := &Handler{store: store, enabled: true}

	request := onboardingRequest(t)
	request.Gin.Request.Header.Set("Accept-Language", "zh-CN")
	response, err := handler.Bootstrap(request)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Equal(t, "zh-CN", store.locale)

	body := response.Body.(dto.DesktopOnboardingResponse)
	require.True(t, body.Eligible)
	require.Equal(t, "v1", body.FlowVersion)
	require.Equal(t, "active", body.Status)
	require.Equal(t, "msg_reply", body.ReplyMessageID)
	require.Equal(t, int64(42), body.ReplySeq)
	require.Equal(t, "chn_reply", body.ReplyScopeID)
}

func TestRoutesExposeDesktopOnboardingLifecycleAndMockExchange(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	RegisterRoutes(router, Dependencies{BillingEnabled: true})

	got := make([]string, 0, len(router.Routes()))
	for _, route := range router.Routes() {
		got = append(got, route.Method+" "+route.Path)
	}
	require.ElementsMatch(t, []string{
		"GET /api/v1/workspaces/:workspaceId/onboarding",
		"POST /api/v1/workspaces/:workspaceId/onboarding/bootstrap",
		"POST /api/v1/workspaces/:workspaceId/onboarding/visibility",
		"POST /api/v1/workspaces/:workspaceId/onboarding/mock-exchange/start",
		"POST /api/v1/workspaces/:workspaceId/onboarding/mock-exchange/reply",
	}, got)
}

func TestBootstrapReturnsIneligibleStateWithoutTutorialChannel(t *testing.T) {
	store := &fakeOnboardingStore{
		ensureEligible: false,
		eligibility:    storeonboarding.EligibilityReasonPreexistingAgentRuntime,
	}
	handler := &Handler{store: store, enabled: true}

	response, err := handler.Bootstrap(onboardingRequest(t))
	require.NoError(t, err)

	body := response.Body.(dto.DesktopOnboardingResponse)
	require.False(t, body.Eligible)
	require.Empty(t, body.TutorialChannelID)
	require.Equal(t, storeonboarding.EligibilityReasonPreexistingAgentRuntime, body.EligibilityReason)
}

func TestBootstrapRejectsUsersNotRegisteredAfterClientCutoffBeforeStoreWrites(t *testing.T) {
	cutoff := time.Date(2026, 8, 9, 0, 0, 0, 0, time.UTC)
	for _, test := range []struct {
		name      string
		createdAt time.Time
		header    string
	}{
		{name: "registration before cutoff", createdAt: cutoff.Add(-time.Second), header: cutoff.Format(time.RFC3339)},
		{name: "registration exactly at cutoff", createdAt: cutoff, header: cutoff.Format(time.RFC3339)},
		{name: "missing cutoff", createdAt: cutoff.Add(time.Second)},
		{name: "malformed cutoff", createdAt: cutoff.Add(time.Second), header: "2026-08-09"},
	} {
		t.Run(test.name, func(t *testing.T) {
			store := &fakeOnboardingStore{state: activeOnboardingState(), ensureEligible: true}
			handler := &Handler{store: store, enabled: true}
			request := onboardingRequest(t)
			request.User.User.CreatedAt = test.createdAt
			request.Gin.Request.Header.Set(desktopOnboardingRegistrationCutoffHeader, test.header)

			response, err := handler.Bootstrap(request)
			require.NoError(t, err)
			body := response.Body.(dto.DesktopOnboardingResponse)
			require.False(t, body.Eligible)
			require.Equal(t, storeonboarding.EligibilityReasonRegistrationCutoff, body.EligibilityReason)
			require.Zero(t, store.ensureCalls)
		})
	}
}

func TestGetBoundsAgentsAndOverlaysRunnableMachineState(t *testing.T) {
	agents := make([]agent.AppAgent, 20)
	for index := range agents {
		agents[index] = agent.AppAgent{
			ID: "agt_" + string(rune('a'+index)), Name: "agent", MachineID: "mch_1", MachineStatus: "offline", CanManage: true,
		}
	}
	agentStore := &fakeOnboardingAgentStore{agents: agents}
	handler := &Handler{
		store:   &fakeOnboardingStore{state: activeOnboardingState(), ensureEligible: true},
		agents:  agentStore,
		runtime: fakeOnboardingRuntime{},
		enabled: true,
		now:     func() time.Time { return time.Date(2026, 7, 29, 0, 0, 0, 0, time.UTC) },
	}

	response, err := handler.Get(onboardingRequest(t))
	require.NoError(t, err)
	require.Equal(t, 20, agentStore.options.Limit)
	require.Equal(t, "usr_1", agentStore.userID)
	require.Equal(t, "wsp_1", agentStore.workspaceID)
	require.Equal(t, "chn_quick", agentStore.options.ChannelID)
	body := response.Body.(dto.DesktopOnboardingResponse)
	require.Len(t, body.Agents, 20)
	require.True(t, body.Agents[0].Runnable)
	require.Equal(t, "online", body.Agents[0].MachineStatus)
}

func TestBootstrapIsUnavailableWhenBillingIsDisabled(t *testing.T) {
	store := &fakeOnboardingStore{state: activeOnboardingState(), ensureEligible: true}
	handler := &Handler{store: store}

	_, err := handler.Bootstrap(onboardingRequest(t))
	require.Error(t, err)
	var httpErr api.HTTPError
	require.ErrorAs(t, err, &httpErr)
	require.Equal(t, http.StatusServiceUnavailable, httpErr.Status)
	require.Empty(t, store.locale)
}

type fakeOnboardingStore struct {
	state          storeonboarding.State
	ensureEligible bool
	eligibility    string
	locale         string
	ensureCalls    int
}

func (store *fakeOnboardingStore) EnsureForUser(_ context.Context, _ string, _ string, locale string) (storeonboarding.EnsureResult, error) {
	store.ensureCalls++
	store.locale = locale
	return storeonboarding.EnsureResult{
		State:             store.state,
		Eligible:          store.ensureEligible,
		EligibilityReason: store.eligibility,
	}, nil
}

func (store *fakeOnboardingStore) GetForUser(context.Context, string, string) (storeonboarding.State, error) {
	return store.state, nil
}

func (store *fakeOnboardingStore) AcknowledgeVisibleReply(context.Context, string, string, string, string) (storeonboarding.State, error) {
	return store.state, nil
}

type fakeOnboardingAgentStore struct {
	agents      []agent.AppAgent
	userID      string
	workspaceID string
	options     agent.AppAgentListOptions
}

func (store *fakeOnboardingAgentStore) ListAgentsForWorkspaceWithOptions(_ context.Context, userID string, workspaceID string, options agent.AppAgentListOptions) ([]agent.AppAgent, error) {
	store.userID = userID
	store.workspaceID = workspaceID
	store.options = options
	return store.agents, nil
}

type fakeOnboardingRuntime struct{}

func (fakeOnboardingRuntime) MachineConnected(string) bool            { return true }
func (fakeOnboardingRuntime) AgentRuntimeRunning(string, string) bool { return true }
func (fakeOnboardingRuntime) AgentRuntimeSessionID(string, string) (string, bool) {
	return "session", true
}

func onboardingRequest(t *testing.T) api.WorkspaceScopedRequest {
	t.Helper()
	ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
	ctx.Request = httptest.NewRequest(http.MethodGet, "/", http.NoBody)
	ctx.Request.Header.Set(desktopOnboardingRegistrationCutoffHeader, "2026-08-09T00:00:00Z")
	return api.WorkspaceScopedRequest{
		AuthenticatedRequest: api.AuthenticatedRequest{Gin: ctx, User: identity.AuthenticatedUser{User: identity.User{
			ID:        "usr_1",
			CreatedAt: time.Date(2026, 8, 9, 0, 0, 1, 0, time.UTC),
		}}},
		Workspace: workspace.Summary{WorkspaceID: "wsp_1", MemberID: "mem_1", MemberUserID: "usr_1", WorkspaceRole: "owner"},
	}
}

func activeOnboardingState() storeonboarding.State {
	return storeonboarding.State{
		UserID:            "usr_1",
		WorkspaceID:       "wsp_1",
		OrganizationID:    "org_1",
		OwnerMemberID:     "mem_1",
		TutorialChannelID: "chn_quick",
		FlowVersion:       storeonboarding.FlowVersionV1,
		Status:            storeonboarding.StatusActive,
		CurrentStep:       storeonboarding.StepCreateAgent,
	}
}
