// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/me/update.go
package me

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/gocql/gocql"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
	uc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
)

type UpdateMeRequestDTO struct {
	Email                                          string `bson:"email" json:"email"`
	FirstName                                      string `bson:"first_name" json:"first_name"`
	LastName                                       string `bson:"last_name" json:"last_name"`
	Phone                                          string `bson:"phone" json:"phone,omitempty"`
	Country                                        string `bson:"country" json:"country,omitempty"`
	Region                                         string `bson:"region" json:"region,omitempty"`
	Timezone                                       string `bson:"timezone" json:"timezone"`
	AgreePromotions                                bool   `bson:"agree_promotions" json:"agree_promotions,omitempty"`
	AgreeToTrackingAcrossThirdPartyAppsAndServices bool   `bson:"agree_to_tracking_across_third_party_apps_and_services" json:"agree_to_tracking_across_third_party_apps_and_services,omitempty"`
	ShareNotificationsEnabled                      *bool  `bson:"share_notifications_enabled" json:"share_notifications_enabled,omitempty"`
}

type UpdateMeService interface {
	Execute(sessCtx context.Context, req *UpdateMeRequestDTO) (*MeResponseDTO, error)
}

type updateMeServiceImpl struct {
	config                *config.Configuration
	logger                *zap.Logger
	userGetByIDUseCase    uc_user.UserGetByIDUseCase
	userGetByEmailUseCase uc_user.UserGetByEmailUseCase
	userUpdateUseCase     uc_user.UserUpdateUseCase
}

func NewUpdateMeService(
	config *config.Configuration,
	logger *zap.Logger,
	userGetByIDUseCase uc_user.UserGetByIDUseCase,
	userGetByEmailUseCase uc_user.UserGetByEmailUseCase,
	userUpdateUseCase uc_user.UserUpdateUseCase,
) UpdateMeService {
	logger = logger.Named("UpdateMeService")

	return &updateMeServiceImpl{
		config:                config,
		logger:                logger,
		userGetByIDUseCase:    userGetByIDUseCase,
		userGetByEmailUseCase: userGetByEmailUseCase,
		userUpdateUseCase:     userUpdateUseCase,
	}
}

func (svc *updateMeServiceImpl) Execute(sessCtx context.Context, req *UpdateMeRequestDTO) (*MeResponseDTO, error) {
	//
	// Get required from context.
	//

	userID, ok := sessCtx.Value(constants.SessionUserID).(gocql.UUID)
	if !ok {
		svc.logger.Error("Failed getting local user id",
			zap.Any("error", "Not found in context: user_id"))
		return nil, errors.New("user id not found in context")
	}

	//
	// STEP 2: Validation
	//

	if req == nil {
		svc.logger.Warn("Failed validation with nothing received")
		return nil, httperror.NewForBadRequestWithSingleField("non_field_error", "Request is required in submission")
	}

	// Sanitization
	req.Email = strings.ToLower(req.Email) // Ensure email is lowercase

	e := make(map[string]string)
	// Add any specific field validations here if needed. Example:
	if req.FirstName == "" {
		e["first_name"] = "First name is required"
	}
	if req.LastName == "" {
		e["last_name"] = "Last name is required"
	}
	if req.Email == "" {
		e["email"] = "Email is required"
	}
	if len(req.Email) > 255 {
		e["email"] = "Email is too long"
	}
	if req.Phone == "" {
		e["phone"] = "Phone confirm is required"
	}
	if req.Country == "" {
		e["country"] = "Country is required"
	}
	if req.Timezone == "" {
		e["timezone"] = "Timezone is required"
	}
	if len(e) != 0 {
		svc.logger.Warn("Failed validation",
			zap.Any("error", e))
		return nil, httperror.NewForBadRequest(&e)
	}

	//
	// Get related records.
	//

	// Get the user account (aka "Me").
	user, err := svc.userGetByIDUseCase.Execute(sessCtx, userID)
	if err != nil {
		// Handle other potential errors during fetch.
		svc.logger.Error("Failed getting user by ID", zap.Any("error", err))
		return nil, err
	}
	// Defensive check, though GetByID should return ErrNoDocuments if not found.
	if user == nil {
		err := fmt.Errorf("user is nil after lookup for id: %v", userID.String())
		svc.logger.Error("Failed getting user", zap.Any("error", err))
		return nil, err
	}

	//
	// Block email changes - must use dedicated email change endpoint
	// Note: Both emails are already lowercase (req.Email was lowercased in validation, user.Email is stored lowercase)
	//
	if strings.ToLower(req.Email) != strings.ToLower(user.Email) {
		svc.logger.Warn("Attempted to change email via profile update",
			zap.String("user_id", userID.String()),
			zap.String("old_email", validation.MaskEmail(user.Email)),
			zap.String("new_email", validation.MaskEmail(req.Email)))
		e["email"] = "Email changes are not allowed through this endpoint. Please use the email change feature in your account settings."
		return nil, httperror.NewForBadRequest(&e)
	}

	//
	// Update local database.
	//

	// Apply changes from request DTO to the user object
	// NOTE: Email is NOT updated here - blocked above
	user.FirstName = req.FirstName
	user.LastName = req.LastName
	user.Name = fmt.Sprintf("%s %s", req.FirstName, req.LastName)
	user.LexicalName = fmt.Sprintf("%s, %s", req.LastName, req.FirstName)
	user.ProfileData.Phone = req.Phone
	user.ProfileData.Country = req.Country
	user.ProfileData.Region = req.Region
	user.Timezone = req.Timezone
	user.ProfileData.AgreePromotions = req.AgreePromotions
	user.ProfileData.AgreeToTrackingAcrossThirdPartyAppsAndServices = req.AgreeToTrackingAcrossThirdPartyAppsAndServices
	if req.ShareNotificationsEnabled != nil {
		user.ProfileData.ShareNotificationsEnabled = req.ShareNotificationsEnabled
	}

	// Persist changes
	if err := svc.userUpdateUseCase.Execute(sessCtx, user); err != nil {
		svc.logger.Error("Failed updating user", zap.Any("error", err), zap.String("user_id", user.ID.String()))
		// Consider mapping specific DB errors (like constraint violations) to HTTP errors if applicable
		return nil, err
	}

	svc.logger.Debug("User updated successfully",
		zap.String("user_id", user.ID.String()))

	// Return updated user details
	return &MeResponseDTO{
		ID:              user.ID,
		Email:           user.Email,
		FirstName:       user.FirstName,
		LastName:        user.LastName,
		Name:            user.Name,
		LexicalName:     user.LexicalName,
		Phone:           user.ProfileData.Phone,
		Country:         user.ProfileData.Country,
		Region:          user.ProfileData.Region,
		Timezone:        user.Timezone,
		AgreePromotions: user.ProfileData.AgreePromotions,
		AgreeToTrackingAcrossThirdPartyAppsAndServices: user.ProfileData.AgreeToTrackingAcrossThirdPartyAppsAndServices,
		ShareNotificationsEnabled:                      user.ProfileData.ShareNotificationsEnabled,
	}, nil
}
