package service

import (
	"context"
	"fmt"

	v1 "template-plus/api/v1"
	"template-plus/api/v1/apperrors"
	error2 "template-plus/api/v1/error"
	"template-plus/internal/model"
	"template-plus/internal/repository"
	"template-plus/pkg/numberutil"
)

type RoleService interface {
	Page(ctx context.Context, pageInfo *v1.RolePageInfo) ([]v1.FullRoleResponse, int64, error)
	ID(ctx context.Context, id int64) (*v1.SimpleRole, error)
	SelectCheckedRoles(ctx context.Context, userId int64) ([]v1.CheckedRoleResponse, error)
	SelectDefault(ctx context.Context) ([]v1.CheckedRoleResponse, error)
	CreateRole(ctx context.Context, request *v1.CreateRoleRequest) error
	UpdateRole(ctx context.Context, request *v1.UpdateRoleRequest) error
	Delete(ctx context.Context, id int64) error
}

func NewRoleService(
	service *Service,
	roleRepo repository.RoleRepository,
	userRoleRepo repository.UserRoleRepository,
	roleButtonRepo repository.RoleButtonRepository,
	roleMenuRepo repository.RoleMenuRepository,
	roleResourceRepo repository.RoleResourceRepository,
	buttonRepo repository.ButtonRepository,
	menuRepo repository.MenuRepository,
	resourceRepo repository.ResourceRepository,
) RoleService {
	return &roleService{
		roleRepo:         roleRepo,
		userRoleRepo:     userRoleRepo,
		roleButtonRepo:   roleButtonRepo,
		roleMenuRepo:     roleMenuRepo,
		roleResourceRepo: roleResourceRepo,
		buttonRepo:       buttonRepo,
		menuRepo:         menuRepo,
		resourceRepo:     resourceRepo,
		Service:          service,
	}
}

type roleService struct {
	roleRepo         repository.RoleRepository
	userRoleRepo     repository.UserRoleRepository
	buttonRepo       repository.ButtonRepository
	roleButtonRepo   repository.RoleButtonRepository
	roleMenuRepo     repository.RoleMenuRepository
	roleResourceRepo repository.RoleResourceRepository
	menuRepo         repository.MenuRepository
	resourceRepo     repository.ResourceRepository
	*Service
}

// Page retrieves a paginated list of roles.
func (r *roleService) Page(ctx context.Context, pageInfo *v1.RolePageInfo) ([]v1.FullRoleResponse, int64, error) {
	roles, total, err := r.roleRepo.SelectRolesPage(ctx, pageInfo)
	if err != nil {
		return nil, 0, apperrors.NewInternalServerError(fmt.Sprintf("failed to list roles (size: %d, offset: %d)", pageInfo.Size, pageInfo.Offset), err)
	}

	responses := make([]v1.FullRoleResponse, len(roles))
	for i, role := range roles {
		responses[i] = v1.RoleToFull(role)
	}
	return responses, total, nil
}

// ID retrieves a single role by their ID.
func (r *roleService) ID(ctx context.Context, id int64) (*v1.SimpleRole, error) {
	role, err := r.roleRepo.GetRoleByID(ctx, id)
	if err != nil {
		return nil, apperrors.NewInternalServerError(fmt.Sprintf("failed to retrieve role ID %d", id), err)
	}
	if role == nil {
		return nil, apperrors.NewNotFoundError(fmt.Sprintf("role ID %d not found", id), err)
	}

	response := v1.RoleToSimple(role)
	return &response, nil
}

func (r *roleService) SelectCheckedRoles(ctx context.Context, userID int64) ([]v1.CheckedRoleResponse, error) {
	roles, err := r.roleRepo.SelectRolesByUserId(ctx, userID)
	if err != nil {
		return nil, apperrors.NewInternalServerError(fmt.Sprintf("failed to retrieve user with ID %d's role", userID), err)
	}
	responses := make([]v1.CheckedRoleResponse, len(roles))
	for i, role := range roles {
		responses[i] = v1.RoleToCheckedRoleResponse(&role)
	}
	return responses, nil
}

func (r *roleService) SelectDefault(ctx context.Context) ([]v1.CheckedRoleResponse, error) {
	roles, err := r.roleRepo.SelectDefault(ctx)
	if err != nil {
		return nil, apperrors.NewInternalServerError("failed to retrieve role list for default role setting", err)
	}
	responses := make([]v1.CheckedRoleResponse, len(roles))
	for i, role := range roles {
		responses[i] = v1.RoleToCheckedRoleResponse(&role)
	}
	return responses, nil
}

func (r *roleService) CreateRole(ctx context.Context, request *v1.CreateRoleRequest) error {
	role := v1.CreateRoleRequestToRole(request)
	menuIDs, err := numberutil.StringSliceToInt64Slice(request.MenuIDs)
	if err != nil {
		return fmt.Errorf("invalid menu IDs: %w", err)
	}
	buttonIDs, err := numberutil.StringSliceToInt64Slice(request.ButtonIDs)
	if err != nil {
		return fmt.Errorf("invalid button IDs: %w", err)
	}
	resourceIDs, err := numberutil.StringSliceToInt64Slice(request.ResourceIDs)
	if err != nil {
		return fmt.Errorf("invalid resource IDs: %w", err)
	}

	return r.tm.Transaction(ctx, func(ctx context.Context) error {
		if _, err = r.roleRepo.InsertRole(ctx, role); err != nil {
			return error2.NewInternalServerError("failed to create role", err)
		}

		if err = r.assignButtonsToRole(ctx, role.ID, buttonIDs, false); err != nil {
			return err
		}
		if err = r.assignMenusToRole(ctx, role.ID, menuIDs, false); err != nil {
			return err
		}
		if err = r.assignResourcesToRole(ctx, role.ID, resourceIDs, false); err != nil {
			return err
		}
		return nil
	})
}

func (r *roleService) UpdateRole(ctx context.Context, request *v1.UpdateRoleRequest) error {
	role := v1.UpdateRoleRequestToRole(request)
	menuIDs, err := numberutil.StringSliceToInt64Slice(request.MenuIDs)
	if err != nil {
		return fmt.Errorf("invalid menu IDs: %w", err)
	}
	buttonIDs, err := numberutil.StringSliceToInt64Slice(request.ButtonIDs)
	if err != nil {
		return fmt.Errorf("invalid button IDs: %w", err)
	}
	resourceIDs, err := numberutil.StringSliceToInt64Slice(request.ResourceIDs)
	if err != nil {
		return fmt.Errorf("invalid resource IDs: %w", err)
	}

	return r.tm.Transaction(ctx, func(ctx context.Context) error {
		if _, err = r.roleRepo.UpdateRole(ctx, role); err != nil {
			return error2.NewInternalServerError(fmt.Sprintf("failed to update role with ID %d", role.ID), err)
		}

		if err = r.assignButtonsToRole(ctx, role.ID, buttonIDs, true); err != nil {
			return err
		}
		if err = r.assignMenusToRole(ctx, role.ID, menuIDs, true); err != nil {
			return err
		}
		if err = r.assignResourcesToRole(ctx, role.ID, resourceIDs, true); err != nil {
			return err
		}

		return nil
	})

}

func (r *roleService) Delete(ctx context.Context, id int64) error {
	return r.tm.Transaction(ctx, func(ctx context.Context) error {
		if _, err := r.userRoleRepo.DeleteUserRoleByRoleID(ctx, id); err != nil {
			return error2.NewInternalServerError(fmt.Sprintf("failed to delete user roles by role ID %d", id), err)
		}
		if _, err := r.roleButtonRepo.DeleteRoleButtonByRoleID(ctx, id); err != nil {
			return error2.NewInternalServerError(fmt.Sprintf("failed to delete role buttons by role ID %d", id), err)
		}
		if _, err := r.roleMenuRepo.DeleteRoleMenuByRoleID(ctx, id); err != nil {
			return error2.NewInternalServerError(fmt.Sprintf("failed to delete role menus by role ID %d", id), err)
		}
		if _, err := r.roleResourceRepo.DeleteRoleResourceByRoleID(ctx, id); err != nil {
			return error2.NewInternalServerError(fmt.Sprintf("failed to delete role resources by role ID %d", id), err)
		}
		if _, err := r.roleRepo.DeleteRole(ctx, id); err != nil {
			return error2.NewInternalServerError(fmt.Sprintf("failed to delete role with ID %d", id), err)
		}
		return nil
	})
}

func (r *roleService) assignButtonsToRole(ctx context.Context, roleID int64, buttonIDs []int64, isUpdate bool) error {
	if isUpdate {
		if _, err := r.roleButtonRepo.DeleteRoleButtonByRoleID(ctx, roleID); err != nil {
			return apperrors.NewInternalServerError(fmt.Sprintf("Failed to delete the buttons associated with the role %d", roleID), err)
		}
	}

	if len(buttonIDs) > 0 {
		roleButtons := make([]model.RoleButton, len(buttonIDs))
		for i, buttonID := range buttonIDs {
			roleButtons[i] = model.RoleButton{RoleID: roleID, ButtonID: buttonID}
		}
		if _, err := r.roleButtonRepo.InsertMany(ctx, roleButtons); err != nil {
			return apperrors.NewInternalServerError(fmt.Sprintf("Failed to assign buttons to role %d", roleID), err)
		}
	}

	return nil
}

func (r *roleService) assignMenusToRole(ctx context.Context, roleID int64, menuIDs []int64, isUpdate bool) error {
	if isUpdate {
		if _, err := r.roleMenuRepo.DeleteRoleMenuByMenuID(ctx, roleID); err != nil {
			return apperrors.NewInternalServerError(fmt.Sprintf("Failed to delete the menus associated with the role %d", roleID), err)
		}
	}

	if len(menuIDs) > 0 {
		roleMenus := make([]model.RoleMenu, len(menuIDs))
		for i, menuID := range menuIDs {
			roleMenus[i] = model.RoleMenu{RoleID: roleID, MenuID: menuID}
		}
		if _, err := r.roleMenuRepo.InsertMany(ctx, roleMenus); err != nil {
			return apperrors.NewInternalServerError(fmt.Sprintf("Failed to assign menus to role %d", roleID), err)
		}
	}

	return nil
}

func (r *roleService) assignResourcesToRole(ctx context.Context, roleID int64, resourceIDs []int64, isUpdate bool) error {
	if isUpdate {
		if _, err := r.roleResourceRepo.DeleteRoleResourceByRoleID(ctx, roleID); err != nil {
			return apperrors.NewInternalServerError(fmt.Sprintf("Failed to delete the resources associated with the role %d", roleID), err)
		}
	}

	if len(resourceIDs) > 0 {
		roleResources := make([]model.RoleResource, len(resourceIDs))
		for i, resourceID := range resourceIDs {
			roleResources[i] = model.RoleResource{RoleID: roleID, ResourceID: resourceID}
		}
		if _, err := r.roleResourceRepo.InsertMany(ctx, roleResources); err != nil {
			return apperrors.NewInternalServerError(fmt.Sprintf("Failed to assign resources to role %d", roleID), err)
		}
	}

	return nil
}
please optimize the code and add comments