package onboarding

import (
	"context"
	"errors"
	"net/http"
	"strings"
	"time"

	"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/message"
	storeonboarding "github.com/tidbcloud/AgentTeam/internal/store/onboarding"
)

const (
	maxOnboardingAgents                       = 20
	desktopOnboardingRegistrationCutoffHeader = "X-AgentTeam-Desktop-Onboarding-Registration-Cutoff"
)

type Handler struct {
	store   Store
	agents  AgentStore
	runtime Runtime
	enabled bool
	now     clock
}

// Get handles GET /api/v1/workspaces/:workspaceId/onboarding.
// It returns the current Desktop onboarding projection without creating it.
func (handler *Handler) Get(request api.WorkspaceScopedRequest) (api.Response, error) {
	if handler == nil || !handler.enabled || handler.store == nil {
		return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Desktop onboarding is unavailable")
	}
	state, err := handler.store.GetForUser(request.Context(), request.UserID(), request.WorkspaceID())
	if err != nil {
		if errors.Is(err, storeonboarding.ErrStateNotFound) {
			return handler.unavailableResponse(request.WorkspaceID(), storeonboarding.EligibilityReasonNotStarted), nil
		}
		return api.Response{}, err
	}
	return handler.responseFromState(request.Context(), request.UserID(), state, true, "")
}

// Bootstrap handles POST /api/v1/workspaces/:workspaceId/onboarding/bootstrap.
// It ensures the private tutorial channel when the user is eligible for the
// Desktop tutorial flow.
func (handler *Handler) Bootstrap(request api.WorkspaceScopedRequest) (api.Response, error) {
	if handler == nil || !handler.enabled || handler.store == nil {
		return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Desktop onboarding is unavailable")
	}
	if !desktopOnboardingRegisteredAfterCutoff(
		request.User.User.CreatedAt,
		request.Gin.GetHeader(desktopOnboardingRegistrationCutoffHeader),
	) {
		return handler.unavailableResponse(request.WorkspaceID(), storeonboarding.EligibilityReasonRegistrationCutoff), nil
	}
	result, err := handler.store.EnsureForUser(
		request.Context(), request.UserID(), request.WorkspaceID(), request.Gin.GetHeader("Accept-Language"),
	)
	if err != nil {
		return api.Response{}, err
	}
	if !result.Eligible {
		return handler.unavailableResponse(request.WorkspaceID(), result.EligibilityReason), nil
	}
	return handler.responseFromState(request.Context(), request.UserID(), result.State, true, "")
}

// Visibility handles POST /api/v1/workspaces/:workspaceId/onboarding/visibility.
// It records that the visible channel UI and authoritative read cursor both
// cover the exact Agent reply returned by the onboarding projection.
func (handler *Handler) Visibility(request api.WorkspaceScopedJSON[dto.DesktopOnboardingVisibilityRequest]) (api.Response, error) {
	if handler == nil || !handler.enabled || handler.store == nil {
		return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Desktop onboarding is unavailable")
	}
	if strings.TrimSpace(request.Body.ScopeID) == "" || strings.TrimSpace(request.Body.ReplyMessageID) == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "scopeId and replyMessageId are required")
	}
	state, err := handler.store.AcknowledgeVisibleReply(
		request.Context(), request.UserID(), request.WorkspaceID(), request.Body.ScopeID, request.Body.ReplyMessageID,
	)
	if err != nil {
		return api.Response{}, err
	}
	if state.UserID == "" {
		return handler.unavailableResponse(request.WorkspaceID(), storeonboarding.EligibilityReasonNotStarted), nil
	}
	return handler.responseFromState(request.Context(), request.UserID(), state, true, "")
}

func (handler *Handler) responseFromState(ctx context.Context, userID string, state storeonboarding.State, eligible bool, eligibilityReason string) (api.Response, error) {
	agents := []agent.AppAgent{}
	if handler.agents != nil && strings.TrimSpace(state.TutorialChannelID) != "" {
		var err error
		agents, err = handler.agents.ListAgentsForWorkspaceWithOptions(ctx, userID, state.WorkspaceID, agent.AppAgentListOptions{
			ChannelID: state.TutorialChannelID,
			Limit:     maxOnboardingAgents,
		})
		if err != nil {
			return api.Response{}, err
		}
		api.OverlayAppAgentsRuntime(agents, handler.runtime, handler.currentTime())
	}
	presented := make([]dto.DesktopOnboardingAgent, 0, len(agents))
	for _, candidate := range agents {
		machineStatus := strings.TrimSpace(candidate.MachineStatus)
		presented = append(presented, dto.DesktopOnboardingAgent{
			ID:            candidate.ID,
			Name:          candidate.Name,
			DisplayName:   candidate.DisplayName,
			MachineID:     candidate.MachineID,
			MachineStatus: machineStatus,
			Status:        candidate.Status,
			Runnable:      machineStatus == "online",
		})
	}
	return api.OK(dto.DesktopOnboardingResponse{
		WorkspaceID:       state.WorkspaceID,
		TutorialChannelID: state.TutorialChannelID,
		Subsystem:         message.ChannelSubsystemTutorial,
		FlowVersion:       state.FlowVersion,
		Status:            state.Status,
		CurrentStep:       state.CurrentStep,
		Eligible:          eligible,
		EligibilityReason: eligibilityReason,
		Completed:         state.Status == storeonboarding.StatusCompleted && state.CompletedAt != nil,
		CompletedAt:       state.CompletedAt,
		ReplyMessageID:    state.Progress.ReplyMessageID,
		ReplySeq:          state.Progress.ReplySeq,
		ReplyScopeID:      state.Progress.ReplyScopeID,
		Agents:            presented,
	}), nil
}

func (handler *Handler) unavailableResponse(workspaceID string, reason string) api.Response {
	if strings.TrimSpace(reason) == "" {
		reason = storeonboarding.EligibilityReasonNotStarted
	}
	return api.OK(dto.DesktopOnboardingResponse{
		WorkspaceID:       strings.TrimSpace(workspaceID),
		Subsystem:         message.ChannelSubsystemTutorial,
		FlowVersion:       storeonboarding.FlowVersionV1,
		Eligible:          false,
		EligibilityReason: reason,
		Agents:            []dto.DesktopOnboardingAgent{},
	})
}

func (handler *Handler) currentTime() time.Time {
	if handler.now != nil {
		return handler.now().UTC()
	}
	return time.Now().UTC()
}

func desktopOnboardingRegisteredAfterCutoff(createdAt time.Time, rawRegistrationCutoff string) bool {
	if createdAt.IsZero() {
		return false
	}
	registrationCutoff, err := time.Parse(time.RFC3339, strings.TrimSpace(rawRegistrationCutoff))
	return err == nil && createdAt.After(registrationCutoff)
}
