package auth

import (
	"context"
	"log/slog"
	"net/http"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/mailer"
	"github.com/tidbcloud/AgentTeam/internal/passwords"
)

// RegistrationHandler owns account creation and registration policy flows.
type RegistrationHandler struct {
	logger                     *slog.Logger
	store                      Store
	authFlow                   AuthFlow
	mailerProvider             func() mailer.Mailer
	emailVerificationRequired  bool
	registrationPolicyProvider func() RegistrationPolicy
	verificationTokenTTL       time.Duration
	sessionTTL                 time.Duration
	passwordAuthDisabled       bool
}

// Register handles POST /api/v1/auth/register.
// Creates a new user account and returns session tokens.
func (handler *RegistrationHandler) Register(ctx *gin.Context) {
	if handler.passwordAuthDisabled {
		api.Error(ctx, http.StatusServiceUnavailable, "Password auth is disabled")
		return
	}

	var request dto.RegisterRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}

	email, err := normalizeEmail(request.Email)
	if err != nil {
		api.Error(ctx, http.StatusBadRequest, err.Error())
		return
	}

	trimmedName := strings.TrimSpace(request.Name)
	if trimmedName == "" {
		api.Error(ctx, http.StatusBadRequest, "name is required")
		return
	}

	password := strings.TrimSpace(request.Password)
	if len(password) < authPasswordMinLength {
		api.Error(ctx, http.StatusBadRequest, passwordTooShortMessage(authPasswordMinLength))
		return
	}

	policy := handler.registrationPolicyValue()
	if policy.HasEmailAllowlist() && !policy.AllowsEmail(email) {
		api.Error(ctx, http.StatusForbidden, "Registration is restricted to allowed email domains for this AgentTeam deployment")
		return
	}

	initialized, err := handler.store.SystemInitialized(ctx.Request.Context())
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	if initialized && !policy.HasEmailAllowlist() && !policy.AllowsOpenRegistration() {
		allowed, err := handler.registrationAllowedForEmail(ctx.Request.Context(), email)
		if err != nil {
			api.StoreError(ctx, err)
			return
		}
		if !allowed {
			api.Error(ctx, http.StatusForbidden, "Registration is invite-only for this AgentTeam deployment; ask an admin to invite this email")
			return
		}
	}

	passwordHash, err := passwords.Hash(password)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}

	accessToken, err := secrets.NewToken(tokenByteLength)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	refreshToken, err := secrets.NewToken(tokenByteLength)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}

	now := time.Now().UTC()

	user, ok := handler.createRegistrationAuthenticatedUser(
		ctx,
		initialized,
		email,
		trimmedName,
		passwordHash,
		accessToken,
		refreshToken,
		now,
		request.DesktopOnboardingEnabled,
		request.DesktopOnboardingRegistrationCutoff,
	)
	if !ok {
		return
	}

	ctx.JSON(http.StatusOK, dto.AuthTokensResponse{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		User:         userResponseFromDomain(user.User),
	})
}

func (handler *RegistrationHandler) registrationAllowedForEmail(ctx context.Context, email string) (bool, error) {
	if handler.authFlow == nil {
		return handler.registrationPolicyValue().AllowsEmail(email), nil
	}
	return handler.authFlow.RegistrationAllowedForEmail(ctx, email, handler.registrationPolicyValue().AllowsEmail(email))
}

func (handler *RegistrationHandler) registrationPolicyValue() RegistrationPolicy {
	if handler.registrationPolicyProvider == nil {
		return NewRegistrationPolicy(RegistrationModeOpen, nil)
	}
	policy := handler.registrationPolicyProvider()
	if policy.IsZero() {
		return NewRegistrationPolicy(RegistrationModeOpen, nil)
	}
	return policy
}

func (handler *RegistrationHandler) mailerValue() mailer.Mailer {
	if handler.mailerProvider == nil {
		return nil
	}
	return handler.mailerProvider()
}
