package auth

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

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/service/authflow"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
)

func (handler *RegistrationHandler) createRegistrationAuthenticatedUser(ctx *gin.Context, initialized bool, email string, trimmedName string, passwordHash string, accessToken string, refreshToken string, now time.Time, desktopOnboardingEnabled bool, desktopOnboardingRegistrationCutoff string) (identity.AuthenticatedUser, bool) {
	if !initialized {
		authenticated, err := handler.store.BootstrapInitialAdmin(
			ctx.Request.Context(),
			email,
			trimmedName,
			trimmedName,
			passwordHash,
			secrets.HashToken(accessToken),
			now.Add(handler.sessionTTL),
		)
		if err != nil {
			api.StoreError(ctx, err)
			return identity.AuthenticatedUser{}, false
		}
		if handler.authFlow == nil {
			_ = handler.store.RevokeSession(ctx.Request.Context(), authenticated.Session.ID)
			api.StoreError(ctx, fmt.Errorf("authflow dependency is not configured"))
			return identity.AuthenticatedUser{}, false
		}
		workspaceOptions := authflow.DefaultWorkspaceOptions{
			ProvisionOnboardingTrial: desktopOnboardingProvisioningEligible(
				authenticated.User,
				desktopOnboardingEnabled,
				desktopOnboardingRegistrationCutoff,
			),
		}
		if err := handler.authFlow.EnsureDefaultWorkspace(ctx.Request.Context(), authenticated.User, workspaceOptions); err != nil {
			_ = handler.store.RevokeSession(ctx.Request.Context(), authenticated.Session.ID)
			api.StoreError(ctx, err)
			return identity.AuthenticatedUser{}, false
		}
		if err := handler.store.CreateRefreshToken(ctx.Request.Context(), authenticated.User.ID, secrets.HashToken(refreshToken), now.Add(handler.sessionTTL)); err != nil {
			_ = handler.store.RevokeSession(ctx.Request.Context(), authenticated.Session.ID)
			api.StoreError(ctx, err)
			return identity.AuthenticatedUser{}, false
		}
		return authenticated, true
	}

	var createdUser identity.AuthenticatedUser
	if handler.emailVerificationRequired {
		if handler.mailerValue() == nil {
			api.ErrorWithCode(ctx, http.StatusInternalServerError, "Mail service is not configured", errorCodeMailerUnavailable)
			return identity.AuthenticatedUser{}, false
		}

		verificationToken, err := secrets.NewToken(tokenByteLength)
		if err != nil {
			api.StoreError(ctx, err)
			return identity.AuthenticatedUser{}, false
		}
		registeredUser, err := handler.store.RegisterUser(
			ctx.Request.Context(),
			email,
			trimmedName,
			trimmedName,
			passwordHash,
			secrets.HashToken(verificationToken),
			now.Add(handler.verificationTokenTTL),
		)
		if err != nil {
			api.StoreError(ctx, err)
			return identity.AuthenticatedUser{}, false
		}
		if err := handler.mailerValue().SendVerificationEmail(ctx.Request.Context(), email, verificationToken); err != nil {
			if handler.logger != nil {
				handler.logger.ErrorContext(ctx.Request.Context(), "send verification email", slog.String("error_class", "verification_email_send_failed"))
			}
			if rollbackErr := handler.store.DeleteUserByID(ctx.Request.Context(), registeredUser.ID); rollbackErr != nil && handler.logger != nil {
				handler.logger.ErrorContext(ctx.Request.Context(), "rollback user after verification email send failure", slog.String("user_id", registeredUser.ID), slog.String("error_class", "user_rollback_failed"))
			}
			api.ErrorWithCode(ctx, http.StatusServiceUnavailable, "Failed to send verification email", errorCodeVerificationEmailSendFailed)
			return identity.AuthenticatedUser{}, false
		}
		createdUser.User = registeredUser
	} else {
		registeredUser, err := handler.store.CreateVerifiedUser(ctx.Request.Context(), email, trimmedName, trimmedName, passwordHash)
		if err != nil {
			api.StoreError(ctx, err)
			return identity.AuthenticatedUser{}, false
		}
		createdUser.User = registeredUser
	}

	if handler.authFlow == nil {
		api.StoreError(ctx, fmt.Errorf("authflow dependency is not configured"))
		return identity.AuthenticatedUser{}, false
	}
	workspaceOptions := authflow.DefaultWorkspaceOptions{
		ProvisionOnboardingTrial: desktopOnboardingProvisioningEligible(
			createdUser.User,
			desktopOnboardingEnabled,
			desktopOnboardingRegistrationCutoff,
		),
	}
	if err := handler.authFlow.EnsureDefaultWorkspace(ctx.Request.Context(), createdUser.User, workspaceOptions); err != nil {
		api.StoreError(ctx, err)
		return identity.AuthenticatedUser{}, false
	}

	session, err := handler.store.CreateSessionAndRefreshToken(
		ctx.Request.Context(),
		createdUser.User.ID,
		secrets.HashToken(accessToken),
		now.Add(handler.sessionTTL),
		secrets.HashToken(refreshToken),
		now.Add(handler.sessionTTL),
	)
	if err != nil {
		api.StoreError(ctx, err)
		return identity.AuthenticatedUser{}, false
	}
	createdUser.Session = session
	return createdUser, true
}

func desktopOnboardingProvisioningEligible(user identity.User, enabled bool, rawRegistrationCutoff string) bool {
	if !enabled || user.CreatedAt.IsZero() {
		return false
	}
	registrationCutoff, err := time.Parse(time.RFC3339, strings.TrimSpace(rawRegistrationCutoff))
	return err == nil && user.CreatedAt.After(registrationCutoff)
}
