// Package onboarding exposes the Desktop-only GUI authentication flow.
package onboarding

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

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/api/auth"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/machine"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/uisession"
	"github.com/tidbcloud/AgentTeam/desktop/internal/userauth"
)

const (
	desktopQuickStartReleaseEnabled           = true
	desktopOnboardingRegistrationCutoff       = "2026-08-09T00:00:00Z"
	desktopOnboardingRegistrationCutoffHeader = "X-AgentTeam-Desktop-Onboarding-Registration-Cutoff"
)

type Dependencies struct {
	Auth            *userauth.Manager
	MachineBindings *machine.Service
	Servers         *profile.ServerManager
	Sessions        *uisession.Store
}

type serverRequest struct {
	ServerURL string `json:"serverUrl" binding:"required"`
	Locale    string `json:"locale"`
}

type loginRequest struct {
	ServerURL  string `json:"serverUrl" binding:"required"`
	Identifier string `json:"identifier" binding:"required"`
	Password   string `json:"password" binding:"required"`
}

type reauthenticateRequest struct {
	ServerID   string `json:"serverId" binding:"required"`
	Identifier string `json:"identifier" binding:"required"`
	Password   string `json:"password" binding:"required"`
}

type registerRequest struct {
	ServerURL string `json:"serverUrl" binding:"required"`
	Name      string `json:"name" binding:"required"`
	Email     string `json:"email" binding:"required"`
	Password  string `json:"password" binding:"required"`
}

type resumeRequest struct {
	AuthID string `json:"authId" binding:"required"`
}

type activateRequest struct {
	AuthID      string `json:"authId" binding:"required"`
	WorkspaceID string `json:"workspaceId" binding:"required"`
}

type createWorkspaceRequest struct {
	OrganizationID string `json:"organizationId" binding:"required"`
	Name           string `json:"name" binding:"required"`
	Slug           string `json:"slug"`
}

type pingCAPAuthStatusRequest struct {
	FlowID string `form:"flowId" binding:"required"`
}

func RegisterRoutes(routes gin.IRoutes, dependencies Dependencies) {
	routes.GET("/onboarding/servers", listServers(dependencies.Auth, dependencies.Servers))
	routes.GET("/onboarding/workspaces", listWorkspaces(dependencies.Auth))
	routes.POST("/onboarding/probe", probe(dependencies.Auth))
	routes.POST("/onboarding/login", login(dependencies.Auth))
	routes.GET("/onboarding/reauthenticate", reauthenticationContext(dependencies))
	routes.POST("/onboarding/reauthenticate", reauthenticate(dependencies))
	routes.POST("/onboarding/register", register(dependencies.Auth))
	routes.POST("/onboarding/pingcap-auth/start", startPingCAPAuth(dependencies.Auth))
	routes.GET("/onboarding/pingcap-auth/status", pingCAPAuthStatus(dependencies.Auth))
	routes.DELETE("/onboarding/pingcap-auth/:flowId", cancelPingCAPAuth(dependencies.Auth))
	routes.POST("/onboarding/resume", resume(dependencies.Auth))
	routes.POST("/onboarding/activate", activate(dependencies))
	routes.GET("/onboarding/accounts/:authId/orgs", listOrganizations(dependencies.Auth))
	routes.POST("/onboarding/accounts/:authId/workspaces", createWorkspace(dependencies.Auth))
	routes.DELETE("/onboarding/session", logoutActive(dependencies))
	routes.DELETE("/onboarding/accounts/:authId", removeAccount(dependencies))
}

func cancelPingCAPAuth(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		manager.CancelPingCAPAuth(c.Param("flowId"))
		c.Status(http.StatusNoContent)
	}
}

func logoutActive(dependencies Dependencies) gin.HandlerFunc {
	return func(c *gin.Context) {
		if dependencies.Auth == nil || dependencies.Sessions == nil {
			unavailable(c)
			return
		}
		sessionID, _, ok := auth.SessionIdentity(c)
		if !ok {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Desktop session is required."})
			return
		}
		snapshot, ok := dependencies.Sessions.Get(sessionID)
		if !ok || snapshot.ActiveServerID == "" {
			c.JSON(http.StatusConflict, gin.H{"error": "No active Desktop workspace login."})
			return
		}
		target, ok := dependencies.Auth.Target(snapshot.ActiveServerID)
		if !ok {
			c.JSON(http.StatusConflict, gin.H{"error": "The active workspace is not a Desktop user login."})
			return
		}
		if err := dependencies.Auth.Logout(c.Request.Context(), target.AuthID, func() {
			dependencies.Sessions.InvalidateServersMatching(func(targetID string) bool {
				return userauth.TargetBelongsToAuth(targetID, target.AuthID)
			})
		}); err != nil {
			failure(c, http.StatusInternalServerError, err)
			return
		}
		c.Status(http.StatusNoContent)
	}
}

func removeAccount(dependencies Dependencies) gin.HandlerFunc {
	return func(c *gin.Context) {
		if dependencies.Auth == nil || dependencies.Sessions == nil {
			unavailable(c)
			return
		}
		authID := strings.TrimSpace(c.Param("authId"))
		if err := dependencies.Auth.Logout(c.Request.Context(), authID, func() {
			dependencies.Sessions.InvalidateServersMatching(func(targetID string) bool {
				return userauth.TargetBelongsToAuth(targetID, authID)
			})
		}); err != nil {
			failure(c, http.StatusNotFound, err)
			return
		}
		c.Status(http.StatusNoContent)
	}
}

func listWorkspaces(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		c.JSON(http.StatusOK, gin.H{"servers": manager.WorkspaceChoices(c.Request.Context())})
	}
}

func listOrganizations(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		organizations, err := manager.ListOrganizations(c.Request.Context(), c.Param("authId"))
		if err != nil {
			failure(c, http.StatusBadGateway, err)
			return
		}
		c.JSON(http.StatusOK, gin.H{"organizations": organizations})
	}
}

func createWorkspace(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var request createWorkspaceRequest
		if !bind(c, &request) {
			return
		}
		workspace, err := manager.CreateWorkspace(
			c.Request.Context(),
			c.Param("authId"),
			request.OrganizationID,
			request.Name,
			request.Slug,
		)
		if err != nil {
			failure(c, http.StatusBadRequest, err)
			return
		}
		c.JSON(http.StatusCreated, workspace)
	}
}

func startPingCAPAuth(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var request serverRequest
		if !bind(c, &request) {
			return
		}
		result, err := manager.BeginPingCAPAuth(
			c.Request.Context(),
			request.ServerURL,
			desktopQuickStartReleaseEnabled,
			desktopOnboardingRegistrationCutoff,
			request.Locale,
		)
		if err != nil {
			failure(c, http.StatusBadGateway, err)
			return
		}
		if c.Request.Context().Err() != nil {
			manager.CancelPingCAPAuth(result.FlowID)
			return
		}
		c.JSON(http.StatusOK, result)
	}
}

func pingCAPAuthStatus(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var request pingCAPAuthStatusRequest
		if err := c.ShouldBindQuery(&request); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "flowId is required."})
			return
		}
		result, err := manager.PingCAPAuthFlowStatus(request.FlowID)
		if err != nil {
			failure(c, http.StatusNotFound, err)
			return
		}
		c.JSON(http.StatusOK, result)
	}
}

func listServers(manager *userauth.Manager, servers *profile.ServerManager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var machineServers []profile.AgentTeamServer
		if servers != nil {
			machineServers = servers.All()
		}
		c.JSON(http.StatusOK, gin.H{"servers": manager.Summaries(machineServers)})
	}
}

func probe(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var request serverRequest
		if !bind(c, &request) {
			return
		}
		result, err := manager.Probe(c.Request.Context(), request.ServerURL)
		if err != nil {
			if errors.Is(err, context.Canceled) && c.Request.Context().Err() != nil {
				return
			}
			failure(c, http.StatusBadGateway, err)
			return
		}
		c.JSON(http.StatusOK, result)
	}
}

func login(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var request loginRequest
		if !bind(c, &request) {
			return
		}
		result, err := manager.Login(c.Request.Context(), request.ServerURL, request.Identifier, request.Password)
		if err != nil {
			failure(c, http.StatusUnauthorized, err)
			return
		}
		c.JSON(http.StatusOK, result)
	}
}

func reauthenticate(dependencies Dependencies) gin.HandlerFunc {
	return func(c *gin.Context) {
		if dependencies.Auth == nil || dependencies.Sessions == nil {
			unavailable(c)
			return
		}
		sessionID, _, ok := auth.SessionIdentity(c)
		if !ok {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Desktop session is required."})
			return
		}
		var request reauthenticateRequest
		if !bind(c, &request) {
			return
		}
		current, ok := dependencies.Sessions.Get(sessionID)
		if !ok || current.ActiveServerID != strings.TrimSpace(request.ServerID) {
			c.JSON(http.StatusConflict, gin.H{"error": "The active Desktop workspace changed."})
			return
		}
		target, ok := dependencies.Auth.Target(current.ActiveServerID)
		if !ok {
			c.JSON(http.StatusConflict, gin.H{"error": "The active workspace is not a Desktop user login."})
			return
		}
		if _, err := dependencies.Auth.Login(
			c.Request.Context(),
			target.ServerURL,
			request.Identifier,
			request.Password,
		); err != nil {
			failure(c, http.StatusUnauthorized, err)
			return
		}
		if latest, exists := dependencies.Sessions.Get(sessionID); !exists ||
			latest.Generation != current.Generation ||
			latest.ActiveServerID != current.ActiveServerID {
			c.JSON(http.StatusConflict, gin.H{"error": "The active Desktop workspace changed."})
			return
		}
		c.Status(http.StatusNoContent)
	}
}

func reauthenticationContext(dependencies Dependencies) gin.HandlerFunc {
	return func(c *gin.Context) {
		if dependencies.Auth == nil || dependencies.Sessions == nil {
			unavailable(c)
			return
		}
		sessionID, _, ok := auth.SessionIdentity(c)
		if !ok {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Desktop session is required."})
			return
		}
		current, ok := dependencies.Sessions.Get(sessionID)
		if !ok || current.ActiveServerID == "" {
			c.JSON(http.StatusConflict, gin.H{"error": "No active Desktop workspace login."})
			return
		}
		target, ok := dependencies.Auth.Target(current.ActiveServerID)
		if !ok {
			c.JSON(http.StatusConflict, gin.H{"error": "The active workspace is not a Desktop user login."})
			return
		}
		probe, err := dependencies.Auth.Probe(c.Request.Context(), target.ServerURL)
		if err != nil {
			failure(c, http.StatusBadGateway, err)
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"serverId":            target.ID,
			"serverUrl":           target.ServerURL,
			"serverName":          target.Name,
			"pingcapAuthEnabled":  probe.PingCAPAuthEnabled,
			"passwordAuthEnabled": probe.PasswordAuthEnabled,
		})
	}
}

func register(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var request registerRequest
		if !bind(c, &request) {
			return
		}
		result, err := manager.Register(
			c.Request.Context(),
			request.ServerURL,
			request.Name,
			request.Email,
			request.Password,
			desktopQuickStartReleaseEnabled,
			desktopOnboardingRegistrationCutoff,
		)
		if err != nil {
			failure(c, http.StatusBadRequest, err)
			return
		}
		c.JSON(http.StatusOK, result)
	}
}

func resume(manager *userauth.Manager) gin.HandlerFunc {
	return func(c *gin.Context) {
		if manager == nil {
			unavailable(c)
			return
		}
		var request resumeRequest
		if !bind(c, &request) {
			return
		}
		result, err := manager.Resume(c.Request.Context(), request.AuthID)
		if err != nil {
			code := userauth.SavedLoginErrorCode(err)
			if code == userauth.SavedLoginErrorReauthenticationRequired {
				c.JSON(http.StatusUnauthorized, gin.H{
					"code":  code,
					"error": "Saved login requires reauthentication.",
				})
				return
			}
			c.JSON(http.StatusServiceUnavailable, gin.H{
				"code":  code,
				"error": "Saved login is temporarily unavailable.",
			})
			return
		}
		c.JSON(http.StatusOK, result)
	}
}

func activate(dependencies Dependencies) gin.HandlerFunc {
	return func(c *gin.Context) {
		if dependencies.Auth == nil || dependencies.Sessions == nil {
			unavailable(c)
			return
		}
		var request activateRequest
		if !bind(c, &request) {
			return
		}
		sessionID, expiresAt, ok := auth.SessionIdentity(c)
		if !ok {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Desktop session is required."})
			return
		}
		targetID, err := dependencies.Auth.ActivateTarget(c.Request.Context(), request.AuthID, request.WorkspaceID)
		if err != nil {
			failure(c, http.StatusBadRequest, err)
			return
		}
		target, _ := dependencies.Auth.Target(targetID)
		machineID := ""
		if dependencies.MachineBindings != nil {
			binding, status, bindingErr := dependencies.MachineBindings.EnsureFirstBinding(
				c.Request.Context(), targetID,
			)
			if bindingErr != nil {
				// Workspace activation must remain available when automatic first-
				// Machine setup is not permitted or temporarily unavailable. The
				// explicit Desktop Machine dialog can retry with actionable feedback.
				if status >= http.StatusInternalServerError {
					failure(c, status, bindingErr)
					return
				}
			}
			if bindingErr == nil {
				machineID = binding.MachineID
			}
		}
		snapshot, err := dependencies.Sessions.Select(sessionID, expiresAt, targetID)
		if err != nil {
			failure(c, http.StatusUnauthorized, err)
			return
		}
		if err := dependencies.Auth.SetLastActiveTarget(targetID); err != nil {
			dependencies.Sessions.ClearIfGeneration(sessionID, snapshot.Generation)
			failure(c, http.StatusInternalServerError, err)
			return
		}
		c.JSON(http.StatusOK, gin.H{
			"server":     gin.H{"id": target.ID, "name": target.Name},
			"binding":    gin.H{"workspaceId": target.WorkspaceID, "machineId": machineID},
			"generation": snapshot.Generation,
		})
	}
}

func bind(c *gin.Context, value any) bool {
	if err := c.ShouldBindJSON(value); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body."})
		return false
	}
	return true
}

func failure(c *gin.Context, status int, err error) {
	message := "Request failed."
	if err != nil && strings.TrimSpace(err.Error()) != "" {
		message = err.Error()
	}
	response := gin.H{"error": message}
	if code, ok := userauth.ServerURLErrorCode(err); ok {
		status = http.StatusBadRequest
		response["code"] = code
	}
	c.JSON(status, response)
}

func unavailable(c *gin.Context) {
	c.JSON(http.StatusServiceUnavailable, gin.H{"error": "Desktop authentication is unavailable."})
}
