package usecase

import (
	"context"

	"gateway/internal/model/permission/domain/entity"
	domrepo "gateway/internal/model/permission/domain/repository"
	dom "gateway/internal/model/permission/domain/usecase"
)

// PolicyReloader is the optional callback invoked after Replace mutates
// role_permissions. RBACUseCase.BroadcastReload satisfies it; passing nil
// disables the post-replace broadcast (useful in tests).
type PolicyReloader func(ctx context.Context, tenantID string) error

// RolePermissionUseCaseParam injects all repos needed to replace role
// permission sets and the reloader hook.
type RolePermissionUseCaseParam struct {
	Roles           domrepo.RoleRepository
	Permissions     domrepo.PermissionRepository
	RolePermissions domrepo.RolePermissionRepository
	Reloader        PolicyReloader
}

type rolePermissionUseCase struct {
	roles     domrepo.RoleRepository
	perms     domrepo.PermissionRepository
	rolePerms domrepo.RolePermissionRepository
	reload    PolicyReloader
}

// NewRolePermissionUseCase returns the role↔permission editor.
func NewRolePermissionUseCase(param RolePermissionUseCaseParam) dom.RolePermissionUseCase {
	return &rolePermissionUseCase{
		roles:     param.Roles,
		perms:     param.Permissions,
		rolePerms: param.RolePermissions,
		reload:    param.Reloader,
	}
}

func (uc *rolePermissionUseCase) List(
	ctx context.Context,
	tenantID, roleID string,
) ([]*entity.Permission, error) {
	if _, err := uc.roles.GetByID(ctx, tenantID, roleID); err != nil {
		return nil, wrapRepoErr(err)
	}
	rps, err := uc.rolePerms.ListByRole(ctx, tenantID, roleID)
	if err != nil {
		return nil, wrapRepoErr(err)
	}
	if len(rps) == 0 {
		return nil, nil
	}
	ids := make([]string, 0, len(rps))
	for _, rp := range rps {
		ids = append(ids, rp.PermissionID)
	}
	perms, err := uc.perms.GetByIDs(ctx, ids)
	if err != nil {
		return nil, wrapRepoErr(err)
	}
	return perms, nil
}

func (uc *rolePermissionUseCase) Replace(
	ctx context.Context,
	tenantID, roleID string,
	permissionIDs []string,
) error {
	role, err := uc.roles.GetByID(ctx, tenantID, roleID)
	if err != nil {
		return wrapRepoErr(err)
	}
	allPerms, err := uc.perms.GetAll(ctx, nil)
	if err != nil {
		return wrapRepoErr(err)
	}
	allowed := make(map[string]struct{}, len(allPerms))
	for _, perm := range allPerms {
		allowed[perm.ID.Hex()] = struct{}{}
	}
	for _, id := range permissionIDs {
		if _, ok := allowed[id]; !ok {
			return errb.ResNotFound("permission not in catalog: " + id)
		}
	}
	closure := getFullParentPermissionIDs(permissionIDs, allPerms)
	if err := uc.rolePerms.SetForRole(ctx, tenantID, roleID, closure); err != nil {
		return wrapRepoErr(err, "set role permissions")
	}
	if uc.reload != nil {
		if err := uc.reload(ctx, role.TenantID); err != nil {
			return wrapRepoErr(err, "reload policy")
		}
	}
	return nil
}

var _ dom.RolePermissionUseCase = (*rolePermissionUseCase)(nil)
