package processor

import (
	"context"
	"fmt"

	"golang.org/x/crypto/bcrypt"

	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/mappers"
	"apskel-pos-be/internal/models"

	"github.com/google/uuid"
)

type UserProcessorImpl struct {
	userRepo         UserRepository
	organizationRepo OrganizationRepository
	outletRepo       OutletRepository
}

func NewUserProcessor(
	userRepo UserRepository,
	organizationRepo OrganizationRepository,
	outletRepo OutletRepository,
) *UserProcessorImpl {
	return &UserProcessorImpl{
		userRepo:         userRepo,
		organizationRepo: organizationRepo,
		outletRepo:       outletRepo,
	}
}

func (p *UserProcessorImpl) CreateUser(ctx context.Context, req *models.CreateUserRequest) (*models.UserResponse, error) {
	_, err := p.organizationRepo.GetByID(ctx, req.OrganizationID)
	if err != nil {
		return nil, fmt.Errorf("organization not found: %w", err)
	}

	if req.OutletID != nil {
		_, err := p.outletRepo.GetByID(ctx, *req.OutletID)
		if err != nil {
			return nil, fmt.Errorf("outlet not found: %w", err)
		}
	}

	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 := mappers.UserCreateRequestToEntity(req, string(passwordHash))

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

	return mappers.UserEntityToResponse(userEntity), nil
}

func (p *UserProcessorImpl) UpdateUser(ctx context.Context, id uuid.UUID, req *models.UpdateUserRequest) (*models.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)
		}
	}

	if req.Name != nil {
		existingUser.Name = *req.Name
	}
	if req.Email != nil {
		existingUser.Email = *req.Email
	}
	if req.Role != nil {
		existingUser.Role = entities.UserRole(*req.Role)
	}
	if req.OutletID != nil {
		existingUser.OutletID = req.OutletID
	}
	if req.IsActive != nil {
		existingUser.IsActive = *req.IsActive
	}
	if req.Permissions != nil {
		existingUser.Permissions = entities.Permissions(*req.Permissions)
	}

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

	return mappers.UserEntityToResponse(existingUser), 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) (*models.UserResponse, error) {
	user, err := p.userRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("user not found: %w", err)
	}

	return mappers.UserEntityToResponse(user), nil
}

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

	return mappers.UserEntityToResponse(user), nil
}

func (p *UserProcessorImpl) ListUsers(ctx context.Context, organizationID uuid.UUID, page, limit int) ([]models.UserResponse, int, error) {
	_, err := p.organizationRepo.GetByID(ctx, organizationID)
	if err != nil {
		return nil, 0, fmt.Errorf("organization not found: %w", err)
	}

	offset := (page - 1) * limit

	filters := map[string]interface{}{
		"organization_id": organizationID,
	}

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

	responses := make([]models.UserResponse, len(users))
	for i, user := range users {
		response := mappers.UserEntityToResponse(user)
		if response != nil {
			responses[i] = *response
		}
	}

	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 *models.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
}

func (p *UserProcessorImpl) UpdateUserOutlet(ctx context.Context, userID uuid.UUID, req *models.UpdateUserOutletRequest) (*models.UserResponse, error) {
	// Get user first to validate existence and get organization_id
	existingUser, err := p.userRepo.GetByID(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("user not found: %w", err)
	}

	// Validate outlet exists
	outlet, err := p.outletRepo.GetByID(ctx, req.OutletID)
	if err != nil {
		return nil, fmt.Errorf("outlet not found: %w", err)
	}

	// Validate outlet belongs to user's organization
	if outlet.OrganizationID != existingUser.OrganizationID {
		return nil, fmt.Errorf("outlet does not belong to user's organization")
	}

	// Update user's outlet_id
	existingUser.OutletID = &req.OutletID

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

	return mappers.UserEntityToResponse(existingUser), nil
}
