package processor

import (
	"context"
	"fmt"

	"golang.org/x/crypto/bcrypt"

	"eslogad-be/internal/contract"
	"eslogad-be/internal/entities"
	"eslogad-be/internal/transformer"

	"github.com/google/uuid"
)

type UserProcessorImpl struct {
	userRepo    UserRepository
	profileRepo UserProfileRepository
}

type UserProfileRepository interface {
	GetByUserID(ctx context.Context, userID uuid.UUID) (*entities.UserProfile, error)
	Create(ctx context.Context, profile *entities.UserProfile) error
	Upsert(ctx context.Context, profile *entities.UserProfile) error
	Update(ctx context.Context, profile *entities.UserProfile) error
}

func NewUserProcessor(
	userRepo UserRepository,
	profileRepo UserProfileRepository,
) *UserProcessorImpl {
	return &UserProcessorImpl{
		userRepo:    userRepo,
		profileRepo: profileRepo,
	}
}

func (p *UserProcessorImpl) CreateUser(ctx context.Context, req *contract.CreateUserRequest) (*contract.UserResponse, error) {
	existingUser, err := p.userRepo.GetByEmail(ctx, req.Email)
	if err == nil && existingUser != nil {
		return nil, fmt.Errorf("user with email %s already exists", req.Email)
	}

	passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		return nil, fmt.Errorf("failed to hash password: %w", err)
	}

	userEntity := transformer.CreateUserRequestToEntity(req, string(passwordHash))

	err = p.userRepo.Create(ctx, userEntity)
	if err != nil {
		return nil, fmt.Errorf("failed to create user: %w", err)
	}

	// create default user profile
	defaultFullName := userEntity.Name
	profile := &entities.UserProfile{
		UserID:            userEntity.ID,
		FullName:          defaultFullName,
		Timezone:          "Asia/Jakarta",
		Locale:            "id-ID",
		Preferences:       entities.JSONB{},
		NotificationPrefs: entities.JSONB{},
	}
	_ = p.profileRepo.Create(ctx, profile)

	return transformer.EntityToContract(userEntity), nil
}

func (p *UserProcessorImpl) UpdateUser(ctx context.Context, id uuid.UUID, req *contract.UpdateUserRequest) (*contract.UserResponse, error) {
	existingUser, err := p.userRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("user not found: %w", err)
	}

	if req.Email != nil && *req.Email != existingUser.Email {
		existingUserByEmail, err := p.userRepo.GetByEmail(ctx, *req.Email)
		if err == nil && existingUserByEmail != nil && existingUserByEmail.ID != id {
			return nil, fmt.Errorf("user with email %s already exists", *req.Email)
		}
	}

	updated := transformer.UpdateUserEntity(existingUser, req)

	err = p.userRepo.Update(ctx, updated)
	if err != nil {
		return nil, fmt.Errorf("failed to update user: %w", err)
	}

	return transformer.EntityToContract(updated), nil
}

func (p *UserProcessorImpl) DeleteUser(ctx context.Context, id uuid.UUID) error {
	_, err := p.userRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("user not found: %w", err)
	}

	err = p.userRepo.Delete(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to delete user: %w", err)
	}

	return nil
}

func (p *UserProcessorImpl) GetUserByID(ctx context.Context, id uuid.UUID) (*contract.UserResponse, error) {
	user, err := p.userRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("user not found: %w", err)
	}
	resp := transformer.EntityToContract(user)
	if resp != nil {
		// Roles are loaded separately since they're not preloaded
		if roles, err := p.userRepo.GetRolesByUserID(ctx, resp.ID); err == nil {
			resp.Roles = transformer.RolesToContract(roles)
		}
		// Departments are now preloaded, so they're already in the response
	}
	return resp, nil
}

func (p *UserProcessorImpl) GetUserByEmail(ctx context.Context, email string) (*contract.UserResponse, error) {
	user, err := p.userRepo.GetByEmail(ctx, email)
	if err != nil {
		return nil, fmt.Errorf("user not found: %w", err)
	}

	// Departments are now preloaded, so they're already in the response
	return transformer.EntityToContract(user), nil
}

func (p *UserProcessorImpl) ListUsersWithFilters(ctx context.Context, req *contract.ListUsersRequest) ([]contract.UserResponse, int, error) {
	page := req.Page
	if page <= 0 {
		page = 1
	}
	limit := req.Limit
	if limit <= 0 {
		limit = 10
	}
	offset := (page - 1) * limit

	users, totalCount, err := p.userRepo.ListWithFilters(ctx, req.Search, req.RoleCode, req.IsActive, limit, offset)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to get users: %w", err)
	}

	responses := transformer.EntitiesToContracts(users)
	userIDs := make([]uuid.UUID, 0, len(responses))
	for i := range responses {
		userIDs = append(userIDs, responses[i].ID)
	}
	// Roles are loaded separately since they're not preloaded
	rolesMap, err := p.userRepo.GetRolesByUserIDs(ctx, userIDs)
	if err == nil {
		for i := range responses {
			if roles, ok := rolesMap[responses[i].ID]; ok {
				responses[i].Roles = transformer.RolesToContract(roles)
			}
		}
	}
	// Departments are now preloaded, so they're already in the responses
	return responses, int(totalCount), nil
}

func (p *UserProcessorImpl) GetUserEntityByEmail(ctx context.Context, email string) (*entities.User, error) {
	user, err := p.userRepo.GetByEmail(ctx, email)
	if err != nil {
		return nil, fmt.Errorf("user not found: %w", err)
	}

	return user, nil
}

func (p *UserProcessorImpl) ChangePassword(ctx context.Context, userID uuid.UUID, req *contract.ChangePasswordRequest) error {
	user, err := p.userRepo.GetByID(ctx, userID)
	if err != nil {
		return fmt.Errorf("user not found: %w", err)
	}

	err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.CurrentPassword))
	if err != nil {
		return fmt.Errorf("current password is incorrect")
	}

	newPasswordHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
	if err != nil {
		return fmt.Errorf("failed to hash new password: %w", err)
	}

	err = p.userRepo.UpdatePassword(ctx, userID, string(newPasswordHash))
	if err != nil {
		return fmt.Errorf("failed to update password: %w", err)
	}

	return nil
}

func (p *UserProcessorImpl) ActivateUser(ctx context.Context, userID uuid.UUID) error {
	_, err := p.userRepo.GetByID(ctx, userID)
	if err != nil {
		return fmt.Errorf("user not found: %w", err)
	}

	err = p.userRepo.UpdateActiveStatus(ctx, userID, true)
	if err != nil {
		return fmt.Errorf("failed to activate user: %w", err)
	}

	return nil
}

func (p *UserProcessorImpl) DeactivateUser(ctx context.Context, userID uuid.UUID) error {
	_, err := p.userRepo.GetByID(ctx, userID)
	if err != nil {
		return fmt.Errorf("user not found: %w", err)
	}

	err = p.userRepo.UpdateActiveStatus(ctx, userID, false)
	if err != nil {
		return fmt.Errorf("failed to deactivate user: %w", err)
	}

	return nil
}

// RBAC implementations
func (p *UserProcessorImpl) GetUserRoles(ctx context.Context, userID uuid.UUID) ([]contract.RoleResponse, error) {
	roles, err := p.userRepo.GetRolesByUserID(ctx, userID)
	if err != nil {
		return nil, err
	}
	return transformer.RolesToContract(roles), nil
}

func (p *UserProcessorImpl) GetUserPermissionCodes(ctx context.Context, userID uuid.UUID) ([]string, error) {
	perms, err := p.userRepo.GetPermissionsByUserID(ctx, userID)
	if err != nil {
		return nil, err
	}
	codes := make([]string, 0, len(perms))
	for _, p := range perms {
		codes = append(codes, p.Code)
	}
	return codes, nil
}

func (p *UserProcessorImpl) GetUserDepartments(ctx context.Context, userID uuid.UUID) ([]contract.DepartmentResponse, error) {
	departments, err := p.userRepo.GetDepartmentsByUserID(ctx, userID)
	if err != nil {
		return nil, err
	}
	return transformer.DepartmentsToContract(departments), nil
}

func (p *UserProcessorImpl) GetUserProfile(ctx context.Context, userID uuid.UUID) (*contract.UserProfileResponse, error) {
	prof, err := p.profileRepo.GetByUserID(ctx, userID)
	if err != nil {
		return nil, err
	}
	return transformer.ProfileEntityToContract(prof), nil
}

func (p *UserProcessorImpl) UpdateUserProfile(ctx context.Context, userID uuid.UUID, req *contract.UpdateUserProfileRequest) (*contract.UserProfileResponse, error) {
	existing, _ := p.profileRepo.GetByUserID(ctx, userID)
	entity := transformer.ProfileUpdateToEntity(userID, req, existing)
	if existing == nil {
		if err := p.profileRepo.Create(ctx, entity); err != nil {
			return nil, err
		}
	} else {
		if err := p.profileRepo.Update(ctx, entity); err != nil {
			return nil, err
		}
	}
	return transformer.ProfileEntityToContract(entity), nil
}

// GetActiveUsersForMention retrieves active users for mention purposes with optional username search
func (p *UserProcessorImpl) GetActiveUsersForMention(ctx context.Context, search *string, limit int) ([]contract.UserResponse, error) {
	if limit <= 0 {
		limit = 50 // Default limit for mention suggestions
	}
	if limit > 100 {
		limit = 100 // Max limit for mention suggestions
	}

	// Set isActive to true to only get active users
	isActive := true
	users, _, err := p.userRepo.ListWithFilters(ctx, search, nil, &isActive, limit, 0)
	if err != nil {
		return nil, fmt.Errorf("failed to get active users: %w", err)
	}

	responses := transformer.EntitiesToContracts(users)
	userIDs := make([]uuid.UUID, 0, len(responses))
	for i := range responses {
		userIDs = append(userIDs, responses[i].ID)
	}

	// Load roles for the users
	rolesMap, err := p.userRepo.GetRolesByUserIDs(ctx, userIDs)
	if err == nil {
		for i := range responses {
			if roles, ok := rolesMap[responses[i].ID]; ok {
				responses[i].Roles = transformer.RolesToContract(roles)
			}
		}
	}

	return responses, nil
}
