package usecase

import (
	"context"

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

	"github.com/zeromicro/go-zero/core/logx"
)

// UserRoleUseCaseParam injects role + user-role repositories.
type UserRoleUseCaseParam struct {
	Roles     domrepo.RoleRepository
	UserRoles domrepo.UserRoleRepository
	Reloader  PolicyReloader
}

type userRoleUseCase struct {
	roles     domrepo.RoleRepository
	userRoles domrepo.UserRoleRepository
	reload    PolicyReloader
}

// NewUserRoleUseCase returns the assignment manager used by tenant
// admins and SyncFromX flows.
func NewUserRoleUseCase(param UserRoleUseCaseParam) dom.UserRoleUseCase {
	return &userRoleUseCase{
		roles:     param.Roles,
		userRoles: param.UserRoles,
		reload:    param.Reloader,
	}
}

func (uc *userRoleUseCase) Assign(ctx context.Context, param *dom.AssignParam) (*entity.UserRole, error) {
	if param == nil || param.TenantID == "" || param.UID == "" || param.RoleID == "" {
		return nil, errb.InputMissingRequired("tenant_id|uid|role_id")
	}
	role, err := uc.roles.GetByID(ctx, param.TenantID, param.RoleID)
	if err != nil {
		return nil, wrapRepoErr(err)
	}
	source := param.Source
	if source == "" {
		source = enum.RoleSourceManual
	}
	if !source.IsValid() {
		return nil, errb.InputInvalidFormat("invalid source")
	}
	ur := &entity.UserRole{
		TenantID: param.TenantID,
		UID:      param.UID,
		RoleID:   role.ID.Hex(),
		Source:   source,
	}
	if err := uc.userRoles.Insert(ctx, ur); err != nil {
		return nil, wrapRepoErr(err, "assign role")
	}
	uc.broadcast(ctx, param.TenantID)
	return ur, nil
}

func (uc *userRoleUseCase) Revoke(ctx context.Context, tenantID, uid, roleID string) error {
	if err := uc.userRoles.Delete(ctx, tenantID, uid, roleID); err != nil {
		return wrapRepoErr(err, "revoke role")
	}
	uc.broadcast(ctx, tenantID)
	return nil
}

func (uc *userRoleUseCase) List(ctx context.Context, tenantID, uid string) ([]*dom.UserRoleSummary, error) {
	rows, err := uc.userRoles.ListByUser(ctx, tenantID, uid)
	if err != nil {
		return nil, wrapRepoErr(err)
	}
	if len(rows) == 0 {
		return nil, nil
	}
	ids := make([]string, 0, len(rows))
	for _, ur := range rows {
		ids = append(ids, ur.RoleID)
	}
	roles, err := uc.roles.ListByTenantAndIDs(ctx, tenantID, ids)
	if err != nil {
		return nil, wrapRepoErr(err)
	}
	roleByID := make(map[string]*entity.Role, len(roles))
	for _, role := range roles {
		roleByID[role.ID.Hex()] = role
	}
	out := make([]*dom.UserRoleSummary, 0, len(rows))
	for _, ur := range rows {
		summary := &dom.UserRoleSummary{UserRole: ur}
		if role, ok := roleByID[ur.RoleID]; ok {
			summary.RoleKey = role.Key
			summary.RoleDisplayName = role.DisplayName
		}
		out = append(out, summary)
	}
	return out, nil
}

func (uc *userRoleUseCase) ReplaceForSource(
	ctx context.Context,
	tenantID, uid string,
	source enum.RoleSource,
	roleKeys []string,
) error {
	if !source.IsValid() {
		return errb.InputInvalidFormat("invalid source")
	}
	if source == enum.RoleSourceManual {
		// Manual assignments are managed via Assign/Revoke; protect from
		// accidental wipe by SyncFromX flows (defence in depth).
		return errb.ResInvalidState("manual source cannot be batch-replaced")
	}
	roleIDs := make([]string, 0, len(roleKeys))
	for _, key := range roleKeys {
		role, err := uc.roles.GetByKey(ctx, tenantID, key)
		if err != nil {
			// Skip unknown keys silently — keeps SyncFromX resilient when
			// the IdP exposes groups the tenant has not mapped yet.
			continue
		}
		roleIDs = append(roleIDs, role.ID.Hex())
	}
	if err := uc.userRoles.ReplaceForSource(ctx, tenantID, uid, source, roleIDs); err != nil {
		return wrapRepoErr(err, "replace user roles")
	}
	uc.broadcast(ctx, tenantID)
	return nil
}

func (uc *userRoleUseCase) broadcast(ctx context.Context, tenantID string) {
	if uc.reload == nil {
		return
	}
	if err := uc.reload(ctx, tenantID); err != nil {
		logx.WithContext(ctx).Errorf("permission user-role: broadcast reload tenant=%s: %v", tenantID, err)
	}
}

var _ dom.UserRoleUseCase = (*userRoleUseCase)(nil)
var _ = domain.ReservedRoleKeyPrefixes // ensure domain package referenced for go build
