package auth

import (
	"errors"
	"io"
	"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"
)

// EmailVerificationHandler owns email verification and password recovery flows.
type EmailVerificationHandler struct {
	logger                    *slog.Logger
	store                     Store
	mailerProvider            func() mailer.Mailer
	emailVerificationRequired bool
	verificationTokenTTL      time.Duration
	passwordAuthDisabled      bool
}

// VerifyEmail handles POST /api/v1/auth/verify-email.
// Verifies a user's email address using a verification token.
func (handler *EmailVerificationHandler) VerifyEmail(ctx *gin.Context) {
	var request dto.EmailVerifyRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}
	token := strings.TrimSpace(request.Token)
	if token == "" {
		api.Error(ctx, http.StatusBadRequest, "token is required")
		return
	}
	if _, err := handler.store.VerifyEmailToken(ctx.Request.Context(), token); err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, dto.EmptyResponse{})
}

// ResendVerification handles POST /api/v1/auth/resend-verification.
// Resends an email verification link to the authenticated user.
func (handler *EmailVerificationHandler) ResendVerification(ctx *gin.Context) {
	authenticated, ok := api.AuthenticatedUserFromContext(ctx)
	if !ok {
		api.Error(ctx, http.StatusUnauthorized, "Unauthorized")
		return
	}

	if !handler.emailVerificationRequired || authenticated.User.EmailVerifiedAt != nil {
		ctx.JSON(http.StatusOK, dto.EmptyResponse{})
		return
	}

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

	token, err := secrets.NewToken(tokenByteLength)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	if err := handler.store.CreateVerificationToken(ctx.Request.Context(), authenticated.User.ID, secrets.HashToken(token), time.Now().UTC().Add(handler.verificationTokenTTL)); err != nil {
		api.StoreError(ctx, err)
		return
	}
	if handler.mailerValue() == nil {
		api.ErrorWithCode(ctx, http.StatusInternalServerError, "Mail service is not configured", errorCodeMailerUnavailable)
		return
	}
	if err := handler.mailerValue().SendVerificationEmail(ctx.Request.Context(), authenticated.User.Email, token); err != nil {
		if handler.logger != nil {
			handler.logger.ErrorContext(ctx.Request.Context(), "send verification email", slog.String("user_id", authenticated.User.ID), slog.String("error_class", "verification_email_send_failed"))
		}
		api.ErrorWithCode(ctx, http.StatusServiceUnavailable, "Failed to send verification email", errorCodeVerificationEmailSendFailed)
		return
	}
	ctx.JSON(http.StatusOK, dto.EmptyResponse{})
}

// ForgotPassword handles POST /api/v1/auth/forgot-password.
// Generates a password reset token and emails a reset link to the user.
func (handler *EmailVerificationHandler) ForgotPassword(ctx *gin.Context) {
	if handler.passwordAuthDisabled {
		api.Error(ctx, http.StatusServiceUnavailable, "Password auth is disabled")
		return
	}

	var request dto.ForgotPasswordRequest
	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
	}

	user, err := handler.store.FindUserByEmail(ctx.Request.Context(), email)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			ctx.JSON(http.StatusOK, dto.EmptyResponse{})
			return
		}
		api.StoreError(ctx, err)
		return
	}

	token, err := secrets.NewToken(tokenByteLength)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	expiresAt := time.Now().UTC().Add(handler.verificationTokenTTL)
	if err := handler.store.CreatePasswordResetToken(ctx.Request.Context(), user.ID, secrets.HashToken(token), expiresAt); err != nil {
		api.StoreError(ctx, err)
		return
	}
	if handler.mailerValue() != nil {
		if err := handler.mailerValue().SendPasswordResetEmail(ctx.Request.Context(), email, token); err != nil && handler.logger != nil {
			handler.logger.WarnContext(ctx.Request.Context(), "send password reset email", slog.String("user_id", user.ID), slog.String("error_class", "password_reset_email_send_failed"))
		}
	}
	ctx.JSON(http.StatusOK, dto.EmptyResponse{})
}

// ResetPassword handles POST /api/v1/auth/reset-password.
// Resets a user's password using a valid reset token.
func (handler *EmailVerificationHandler) ResetPassword(ctx *gin.Context) {
	if handler.passwordAuthDisabled {
		api.Error(ctx, http.StatusServiceUnavailable, "Password auth is disabled")
		return
	}

	var request dto.ResetPasswordRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		api.Error(ctx, http.StatusBadRequest, "Invalid request body")
		return
	}
	token := strings.TrimSpace(request.Token)
	if token == "" {
		api.Error(ctx, http.StatusBadRequest, "token is required")
		return
	}
	password := strings.TrimSpace(request.Password)
	if len(password) < authPasswordMinLength {
		api.Error(ctx, http.StatusBadRequest, passwordTooShortMessage(authPasswordMinLength))
		return
	}
	passwordHash, err := passwords.Hash(password)
	if err != nil {
		api.StoreError(ctx, err)
		return
	}
	if err := handler.store.ResetPasswordWithToken(ctx.Request.Context(), token, passwordHash); err != nil {
		api.StoreError(ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, dto.EmptyResponse{})
}

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