package com.yaoai.admin.tenant;

import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.yaoai.admin.auth.StpAdminUtil;
import com.yaoai.admin.common.PageResult;
import com.yaoai.admin.tenant.dto.TenantBalanceOverview;
import com.yaoai.admin.tenant.dto.TenantCreateRequest;
import com.yaoai.admin.tenant.dto.TenantDetail;
import com.yaoai.admin.tenant.dto.TenantImpersonateRequest;
import com.yaoai.admin.tenant.dto.TenantImpersonateResponse;
import com.yaoai.admin.tenant.dto.TenantListItem;
import com.yaoai.admin.tenant.dto.TenantMemberImportResult;
import com.yaoai.admin.tenant.dto.TenantMemberRequest;
import com.yaoai.admin.tenant.dto.TenantMemberRow;
import com.yaoai.admin.tenant.dto.TenantPlanChangeRequest;
import com.yaoai.admin.tenant.dto.TenantQuotaUpdateRequest;
import com.yaoai.admin.tenant.dto.TenantRechargeRequest;
import com.yaoai.admin.tenant.dto.TenantSuspendRequest;
import com.yaoai.admin.tenant.dto.TenantUpdateRequest;
import com.yaoai.billing.service.BillingService;
import com.yaoai.common.exception.BizException;
import com.yaoai.common.exception.ErrorCode;
import com.yaoai.domain.entity.BillingRecord;
import com.yaoai.domain.entity.Plan;
import com.yaoai.domain.entity.Project;
import com.yaoai.domain.entity.Tenant;
import com.yaoai.domain.entity.TenantBalance;
import com.yaoai.domain.entity.TenantMember;
import com.yaoai.domain.entity.TenantQuota;
import com.yaoai.domain.entity.User;
import com.yaoai.domain.mapper.BillingRecordMapper;
import com.yaoai.domain.mapper.PlanMapper;
import com.yaoai.domain.mapper.ProjectMapper;
import com.yaoai.domain.mapper.TenantBalanceMapper;
import com.yaoai.domain.mapper.TenantMemberMapper;
import com.yaoai.domain.mapper.TenantMapper;
import com.yaoai.domain.mapper.TenantQuotaMapper;
import com.yaoai.domain.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Service
@RequiredArgsConstructor
public class TenantAdminService {

    private final TenantMapper tenantMapper;
    private final UserMapper userMapper;
    private final ProjectMapper projectMapper;
    private final TenantBalanceMapper tenantBalanceMapper;
    private final TenantMemberMapper tenantMemberMapper;
    private final BillingRecordMapper billingRecordMapper;
    private final PlanMapper planMapper;
    private final TenantQuotaMapper tenantQuotaMapper;
    private final BillingService billingService;
    private final JdbcTemplate jdbcTemplate;

    private static final BigDecimal MAX_SINGLE_RECHARGE = new BigDecimal("10000000");
    private static final long IMPERSONATE_TTL_SECONDS = 1800;

    public PageResult<TenantListItem> list(String keyword, String plan, Integer status, long page, long size) {
        long safePage = Math.max(1, page);
        long safeSize = Math.max(1, Math.min(size, 100));
        long offset = (safePage - 1) * safeSize;

        long total = tenantMapper.selectCount(buildTenantQuery(keyword, plan, status));
        List<TenantListItem> records = tenantMapper.selectList(buildTenantQuery(keyword, plan, status)
                        .orderByDesc(Tenant::getCreatedAt)
                        .last("LIMIT " + safeSize + " OFFSET " + offset))
                .stream()
                .map(this::toListItem)
                .toList();
        return new PageResult<>(records, total, safePage, safeSize);
    }

    public TenantDetail detail(Long tenantId) {
        Tenant tenant = requireTenant(tenantId);
        return TenantDetail.builder()
                .tenant(tenant)
                .balance(balanceOrEmpty(tenantId))
                .quota(tenantQuotaMapper.selectById(tenantId))
                .memberCount(countMembers(tenantId))
                .projectCount(countProjects(tenantId))
                .build();
    }

    @Transactional
    public TenantDetail create(TenantCreateRequest request) {
        if (request == null || !StringUtils.hasText(request.getName()) || !StringUtils.hasText(request.getSlug())) {
            throw new BizException(ErrorCode.INVALID_PARAM, "企业名称和 slug 必填");
        }
        String slug = request.getSlug().trim().toLowerCase();
        if (tenantMapper.selectOne(Wrappers.<Tenant>lambdaQuery().eq(Tenant::getSlug, slug)) != null) {
            throw new BizException(ErrorCode.INVALID_PARAM, "企业 slug 已存在");
        }
        String planCode = StringUtils.hasText(request.getPlanCode()) ? request.getPlanCode().trim().toUpperCase() : "FREE";
        Plan plan = planMapper.selectOne(Wrappers.<Plan>lambdaQuery()
                .eq(Plan::getCode, planCode)
                .eq(Plan::getIsActive, 1));
        if (plan == null) {
            throw new BizException(ErrorCode.INVALID_PARAM, "套餐不存在或已停用");
        }

        LocalDateTime now = LocalDateTime.now();
        BigDecimal initialCredits = request.getInitialCredits() == null ? BigDecimal.ZERO : request.getInitialCredits();
        Tenant tenant = new Tenant();
        tenant.setName(request.getName().trim());
        tenant.setSlug(slug);
        tenant.setPlan(planCode.toLowerCase());
        tenant.setStatus(1);
        tenant.setCredits(initialCredits);
        tenant.setFrozenCredits(BigDecimal.ZERO);
        tenant.setNotes(request.getNotes());
        tenant.setCreatedAt(now);
        tenant.setUpdatedAt(now);
        tenantMapper.insert(tenant);

        TenantBalance balance = new TenantBalance();
        balance.setTenantId(tenant.getId());
        balance.setBalance(initialCredits);
        balance.setTotalRecharged(initialCredits);
        balance.setTotalSpent(BigDecimal.ZERO);
        balance.setVersion(0);
        balance.setUpdatedAt(now);
        tenantBalanceMapper.insert(balance);

        TenantQuota quota = new TenantQuota();
        quota.setTenantId(tenant.getId());
        quota.setMaxMembers(plan.getMaxMembers());
        quota.setMaxProjects(plan.getMaxProjects());
        quota.setMaxMonthlyCredits(plan.getMonthlyCredits());
        quota.setMaxConcurrentTasks(plan.getMaxConcurrentTasks());
        quota.setUpdatedAt(now);
        quota.setUpdatedBy(StpAdminUtil.getLoginIdAsLong());
        tenantQuotaMapper.insert(quota);

        User owner = new User();
        owner.setTenantId(tenant.getId());
        owner.setUsername(StringUtils.hasText(request.getAdminUsername()) ? request.getAdminUsername().trim() : slug + "_admin");
        owner.setEmail(StringUtils.hasText(request.getAdminEmail()) ? request.getAdminEmail().trim() : slug + "@dramix.local");
        owner.setPasswordHash(BCrypt.hashpw(StringUtils.hasText(request.getAdminPassword()) ? request.getAdminPassword() : "Dramix@123", BCrypt.gensalt()));
        owner.setRole("owner");
        owner.setStatus(1);
        owner.setCreatedAt(now);
        owner.setUpdatedAt(now);
        userMapper.insert(owner);

        TenantMember member = new TenantMember();
        member.setTenantId(tenant.getId());
        member.setUserId(owner.getId());
        member.setRole("owner");
        member.setPosition("企业管理员");
        member.setJoinedAt(now);
        tenantMemberMapper.insert(member);

        if (initialCredits.compareTo(BigDecimal.ZERO) > 0) {
            BillingRecord record = new BillingRecord();
            record.setTenantId(tenant.getId());
            record.setUserId(owner.getId());
            record.setCreatedByAdminId(StpAdminUtil.getLoginIdAsLong());
            record.setOperation("recharge");
            record.setModality("RECHARGE");
            record.setModelProvider("admin");
            record.setModelId("manual_opening");
            record.setModelName("手动开户赠送");
            record.setUnitCount(1);
            record.setSuccess(true);
            record.setCredits(initialCredits);
            record.setBillingMode("SOFT");
            record.setRefId("OPENING:" + tenant.getId());
            record.setCreatedAt(now);
            billingRecordMapper.insert(record);
        }

        return detail(tenant.getId());
    }

    @Transactional
    public Tenant update(Long tenantId, TenantUpdateRequest request) {
        Tenant tenant = requireTenant(tenantId);
        if (StringUtils.hasText(request.getName())) {
            tenant.setName(request.getName());
        }
        if (request.getNotes() != null) {
            tenant.setNotes(request.getNotes());
        }
        tenant.setUpdatedAt(LocalDateTime.now());
        tenantMapper.updateById(tenant);
        return tenantMapper.selectById(tenantId);
    }

    @Transactional
    public Tenant changePlan(Long tenantId, TenantPlanChangeRequest request) {
        Tenant tenant = requireTenant(tenantId);
        String planCode = request.getPlanCode() == null ? "" : request.getPlanCode().trim().toUpperCase();
        Plan plan = planMapper.selectOne(Wrappers.<Plan>lambdaQuery()
                .eq(Plan::getCode, planCode)
                .eq(Plan::getIsActive, 1));
        if (plan == null) {
            throw new BizException(ErrorCode.INVALID_PARAM, "套餐不存在或已停用");
        }

        tenant.setPlan(planCode.toLowerCase());
        tenant.setUpdatedAt(LocalDateTime.now());
        tenantMapper.updateById(tenant);

        TenantQuota quota = tenantQuotaMapper.selectById(tenantId);
        if (quota == null) {
            quota = new TenantQuota();
            quota.setTenantId(tenantId);
        }
        quota.setMaxMembers(plan.getMaxMembers());
        quota.setMaxProjects(plan.getMaxProjects());
        quota.setMaxMonthlyCredits(plan.getMonthlyCredits());
        quota.setMaxConcurrentTasks(plan.getMaxConcurrentTasks());
        quota.setUpdatedAt(LocalDateTime.now());
        quota.setUpdatedBy(StpAdminUtil.getLoginIdAsLong());
        if (tenantQuotaMapper.selectById(tenantId) == null) {
            tenantQuotaMapper.insert(quota);
        } else {
            tenantQuotaMapper.updateById(quota);
        }
        return tenantMapper.selectById(tenantId);
    }

    @Transactional
    public Tenant suspend(Long tenantId, TenantSuspendRequest request) {
        Tenant tenant = requireTenant(tenantId);
        tenant.setStatus(0);
        tenant.setSuspendedAt(LocalDateTime.now());
        tenant.setSuspendedBy(StpAdminUtil.getLoginIdAsLong());
        tenant.setSuspendedReason(request.getReason());
        tenant.setUpdatedAt(LocalDateTime.now());
        tenantMapper.updateById(tenant);

        userMapper.findByTenant(tenantId).forEach(user -> StpUtil.kickout(user.getId()));
        return tenantMapper.selectById(tenantId);
    }

    @Transactional
    public Tenant resume(Long tenantId) {
        Tenant tenant = requireTenant(tenantId);
        tenant.setStatus(1);
        tenant.setSuspendedAt(null);
        tenant.setSuspendedBy(null);
        tenant.setSuspendedReason(null);
        tenant.setUpdatedAt(LocalDateTime.now());
        tenantMapper.updateById(tenant);
        return tenantMapper.selectById(tenantId);
    }

    public TenantBalanceOverview balance(Long tenantId) {
        requireTenant(tenantId);
        List<BillingRecord> recent = billingRecordMapper.selectList(Wrappers.<BillingRecord>lambdaQuery()
                .eq(BillingRecord::getTenantId, tenantId)
                .ge(BillingRecord::getCreatedAt, LocalDateTime.now().minusDays(30))
                .orderByDesc(BillingRecord::getCreatedAt)
                .last("LIMIT 100"));
        return TenantBalanceOverview.builder()
                .balance(balanceOrEmpty(tenantId))
                .recentRecords(recent)
                .build();
    }

    public List<TenantMemberRow> members(Long tenantId) {
        requireTenant(tenantId);
        return jdbcTemplate.query("""
                select tm.id as member_id,
                       u.id as user_id,
                       u.tenant_id,
                       u.username,
                       u.email,
                       u.phone,
                       u.role as account_role,
                       tm.role as member_role,
                       tm.position,
                       u.status,
                       u.last_login_at,
                       tm.joined_at
                  from users u
                  left join tenant_members tm on tm.tenant_id = u.tenant_id and tm.user_id = u.id
                 where u.tenant_id = ?
                   and u.deleted_at is null
                 order by case coalesce(tm.role, u.role) when 'owner' then 1 when 'admin' then 2 else 3 end,
                          u.created_at asc
                """, BeanPropertyRowMapper.newInstance(TenantMemberRow.class), tenantId);
    }

    @Transactional
    public TenantMemberRow createMember(Long tenantId, TenantMemberRequest request) {
        requireTenant(tenantId);
        validateMemberRequest(request, true);
        if (userMapper.existsByEmail(request.getEmail().trim())) {
            throw new BizException(ErrorCode.INVALID_PARAM, "邮箱已被其他账号使用");
        }
        if (userMapper.existsByUsernameAndTenant(request.getUsername().trim(), tenantId)) {
            throw new BizException(ErrorCode.INVALID_PARAM, "该租户下用户名已存在");
        }
        enforceMemberQuota(tenantId);

        LocalDateTime now = LocalDateTime.now();
        String role = normalizeMemberRole(request.getRole());
        User user = new User();
        user.setTenantId(tenantId);
        user.setUsername(request.getUsername().trim());
        user.setEmail(request.getEmail().trim());
        user.setPhone(StringUtils.hasText(request.getPhone()) ? request.getPhone().trim() : null);
        user.setPasswordHash(BCrypt.hashpw(StringUtils.hasText(request.getPassword()) ? request.getPassword() : "Dramix@123", BCrypt.gensalt()));
        user.setRole(role);
        user.setStatus(request.getStatus() == null ? 1 : request.getStatus());
        user.setCreatedAt(now);
        user.setUpdatedAt(now);
        userMapper.insert(user);

        TenantMember member = new TenantMember();
        member.setTenantId(tenantId);
        member.setUserId(user.getId());
        member.setRole(role);
        member.setPosition(StringUtils.hasText(request.getPosition()) ? request.getPosition().trim() : defaultPosition(role));
        member.setJoinedAt(now);
        tenantMemberMapper.insert(member);
        return memberRow(tenantId, user.getId());
    }

    @Transactional
    public List<TenantMemberImportResult> importMembers(Long tenantId, List<TenantMemberRequest> requests) {
        requireTenant(tenantId);
        if (requests == null || requests.isEmpty()) {
            throw new BizException(ErrorCode.INVALID_PARAM, "导入成员不能为空");
        }
        if (requests.size() > 200) {
            throw new BizException(ErrorCode.INVALID_PARAM, "单次最多导入 200 名成员");
        }

        List<TenantMemberImportResult> results = new ArrayList<>();
        Set<String> usernames = new HashSet<>();
        Set<String> emails = new HashSet<>();
        for (int i = 0; i < requests.size(); i++) {
            TenantMemberRequest request = requests.get(i);
            int rowNumber = i + 2;
            String username = request == null ? null : trimToNull(request.getUsername());
            String email = request == null ? null : trimToNull(request.getEmail());
            try {
                if (username != null && !usernames.add(username.toLowerCase())) {
                    throw new BizException(ErrorCode.INVALID_PARAM, "表格内用户名重复");
                }
                if (email != null && !emails.add(email.toLowerCase())) {
                    throw new BizException(ErrorCode.INVALID_PARAM, "表格内邮箱重复");
                }
                TenantMemberRow member = createMember(tenantId, request);
                results.add(TenantMemberImportResult.builder()
                        .rowNumber(rowNumber)
                        .username(member.getUsername())
                        .email(member.getEmail())
                        .success(true)
                        .message("导入成功")
                        .member(member)
                        .build());
            } catch (Exception ex) {
                results.add(TenantMemberImportResult.builder()
                        .rowNumber(rowNumber)
                        .username(username)
                        .email(email)
                        .success(false)
                        .message(ex.getMessage())
                        .build());
            }
        }
        return results;
    }

    @Transactional
    public TenantMemberRow updateMember(Long tenantId, Long userId, TenantMemberRequest request) {
        requireTenant(tenantId);
        User user = requireTenantUser(tenantId, userId);
        TenantMember member = tenantMemberMapper.findByTenantAndUser(tenantId, userId)
                .orElseGet(() -> {
                    TenantMember created = new TenantMember();
                    created.setTenantId(tenantId);
                    created.setUserId(userId);
                    created.setJoinedAt(LocalDateTime.now());
                    return created;
                });

        if (StringUtils.hasText(request.getUsername()) && !request.getUsername().trim().equals(user.getUsername())) {
            if (userMapper.existsByUsernameAndTenant(request.getUsername().trim(), tenantId)) {
                throw new BizException(ErrorCode.INVALID_PARAM, "该租户下用户名已存在");
            }
            user.setUsername(request.getUsername().trim());
        }
        if (StringUtils.hasText(request.getEmail()) && !request.getEmail().trim().equals(user.getEmail())) {
            if (userMapper.existsByEmail(request.getEmail().trim())) {
                throw new BizException(ErrorCode.INVALID_PARAM, "邮箱已被其他账号使用");
            }
            user.setEmail(request.getEmail().trim());
        }
        if (request.getPhone() != null) {
            user.setPhone(StringUtils.hasText(request.getPhone()) ? request.getPhone().trim() : null);
        }
        if (StringUtils.hasText(request.getPassword())) {
            user.setPasswordHash(BCrypt.hashpw(request.getPassword(), BCrypt.gensalt()));
            StpUtil.kickout(userId);
        }
        if (request.getStatus() != null) {
            if (request.getStatus() != 0 && request.getStatus() != 1) {
                throw new BizException(ErrorCode.INVALID_PARAM, "成员状态必须为 0 或 1");
            }
            user.setStatus(request.getStatus());
            if (request.getStatus() == 0) {
                StpUtil.kickout(userId);
            }
        }
        if (StringUtils.hasText(request.getRole())) {
            String role = normalizeMemberRole(request.getRole());
            user.setRole(role);
            member.setRole(role);
        } else if (!StringUtils.hasText(member.getRole())) {
            member.setRole(user.getRole());
        }
        if (request.getPosition() != null) {
            member.setPosition(StringUtils.hasText(request.getPosition()) ? request.getPosition().trim() : defaultPosition(member.getRole()));
        }
        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);
        if (member.getId() == null) {
            tenantMemberMapper.insert(member);
        } else {
            tenantMemberMapper.updateById(member);
        }
        return memberRow(tenantId, userId);
    }

    @Transactional
    public void deleteMember(Long tenantId, Long userId) {
        requireTenant(tenantId);
        User user = requireTenantUser(tenantId, userId);
        String role = user.getRole();
        if ("owner".equalsIgnoreCase(role) && countActiveOwners(tenantId) <= 1) {
            throw new BizException(ErrorCode.INVALID_PARAM, "至少保留一名企业所有者");
        }
        TenantMember member = tenantMemberMapper.findByTenantAndUser(tenantId, userId).orElse(null);
        if (member != null) {
            tenantMemberMapper.deleteById(member.getId());
        }
        user.setStatus(0);
        user.setDeletedAt(LocalDateTime.now());
        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);
        StpUtil.kickout(userId);
    }

    public List<Project> projects(Long tenantId) {
        requireTenant(tenantId);
        return projectMapper.findByTenant(tenantId);
    }

    @Transactional
    public TenantBalanceOverview recharge(Long tenantId, TenantRechargeRequest request) {
        Tenant tenant = requireTenant(tenantId);
        if (Integer.valueOf(0).equals(tenant.getStatus())) {
            throw new BizException(ErrorCode.INVALID_PARAM, "暂停租户不允许充值");
        }
        BigDecimal amount = request.getAmount();
        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BizException(ErrorCode.INVALID_PARAM, "充值金额必须大于 0");
        }
        if (amount.compareTo(MAX_SINGLE_RECHARGE) > 0) {
            throw new BizException(ErrorCode.INVALID_PARAM, "单笔充值不能超过 1000 万积分");
        }

        Long adminId = StpAdminUtil.getLoginIdAsLong();
        String refId = StringUtils.hasText(request.getExternalRefId())
                ? request.getExternalRefId()
                : "ADMIN:" + adminId + ":" + System.currentTimeMillis();
        billingService.rechargeByAdmin(tenantId, amount, adminId, refId);
        return balance(tenantId);
    }

    @Transactional
    public TenantQuota updateQuota(Long tenantId, TenantQuotaUpdateRequest request) {
        requireTenant(tenantId);
        TenantQuota quota = tenantQuotaMapper.selectById(tenantId);
        if (quota == null) {
            quota = new TenantQuota();
            quota.setTenantId(tenantId);
        }
        quota.setMaxMembers(request.getMaxMembers());
        quota.setMaxProjects(request.getMaxProjects());
        quota.setMaxMonthlyCredits(request.getMaxMonthlyCredits());
        quota.setMaxConcurrentTasks(request.getMaxConcurrentTasks());
        quota.setMonthlyBudgetLimit(request.getMonthlyBudgetLimit());
        quota.setNotes(request.getNotes());
        quota.setUpdatedAt(LocalDateTime.now());
        quota.setUpdatedBy(StpAdminUtil.getLoginIdAsLong());
        if (tenantQuotaMapper.selectById(tenantId) == null) {
            tenantQuotaMapper.insert(quota);
        } else {
            tenantQuotaMapper.updateById(quota);
        }
        return tenantQuotaMapper.selectById(tenantId);
    }

    public TenantImpersonateResponse impersonate(Long tenantId, TenantImpersonateRequest request) {
        Tenant tenant = requireTenant(tenantId);
        if (Integer.valueOf(0).equals(tenant.getStatus())) {
            throw new BizException(ErrorCode.INVALID_PARAM, "暂停租户不允许模拟登录");
        }
        User user = resolveImpersonateUser(tenantId, request.getUserId());
        Long adminId = StpAdminUtil.getLoginIdAsLong();

        StpUtil.login(user.getId(), SaLoginModel.create()
                .setTimeout(IMPERSONATE_TTL_SECONDS)
                .setIsLastingCookie(false)
                .setExtra("tenantId", tenantId)
                .setExtra("impersonated", true)
                .setExtra("impersonatedBy", adminId));
        StpUtil.getSession()
                .set("tenantId", tenantId)
                .set("impersonated", true)
                .set("impersonatedBy", adminId);

        return TenantImpersonateResponse.builder()
                .token(StpUtil.getTokenValue())
                .tenantId(tenantId)
                .userId(user.getId())
                .ttlSeconds(IMPERSONATE_TTL_SECONDS)
                .build();
    }

    private LambdaQueryWrapper<Tenant> buildTenantQuery(String keyword, String plan, Integer status) {
        LambdaQueryWrapper<Tenant> query = Wrappers.<Tenant>lambdaQuery()
                .isNull(Tenant::getDeletedAt);
        if (StringUtils.hasText(keyword)) {
            query.and(q -> q.like(Tenant::getName, keyword).or().like(Tenant::getSlug, keyword));
        }
        if (StringUtils.hasText(plan)) {
            query.eq(Tenant::getPlan, plan.trim().toLowerCase());
        }
        if (status != null) {
            query.eq(Tenant::getStatus, status);
        }
        return query;
    }

    private Tenant requireTenant(Long tenantId) {
        Tenant tenant = tenantMapper.selectOne(Wrappers.<Tenant>lambdaQuery()
                .eq(Tenant::getId, tenantId)
                .isNull(Tenant::getDeletedAt));
        if (tenant == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "租户不存在");
        }
        return tenant;
    }

    private TenantListItem toListItem(Tenant tenant) {
        TenantBalance balance = tenantBalanceMapper.findByTenantId(tenant.getId());
        BigDecimal currentBalance = balance != null ? balance.getBalance() : tenant.getCredits();
        return TenantListItem.builder()
                .id(tenant.getId())
                .name(tenant.getName())
                .slug(tenant.getSlug())
                .plan(tenant.getPlan())
                .status(tenant.getStatus())
                .balance(currentBalance == null ? BigDecimal.ZERO : currentBalance)
                .memberCount(countMembers(tenant.getId()))
                .projectCount(countProjects(tenant.getId()))
                .createdAt(tenant.getCreatedAt())
                .build();
    }

    private TenantBalance balanceOrEmpty(Long tenantId) {
        TenantBalance balance = tenantBalanceMapper.findByTenantId(tenantId);
        if (balance != null) {
            return balance;
        }
        TenantBalance empty = new TenantBalance();
        empty.setTenantId(tenantId);
        empty.setBalance(BigDecimal.ZERO);
        empty.setTotalRecharged(BigDecimal.ZERO);
        empty.setTotalSpent(BigDecimal.ZERO);
        return empty;
    }

    private long countMembers(Long tenantId) {
        return userMapper.selectCount(Wrappers.<User>lambdaQuery()
                .eq(User::getTenantId, tenantId)
                .isNull(User::getDeletedAt));
    }

    private long countProjects(Long tenantId) {
        return projectMapper.selectCount(Wrappers.<Project>lambdaQuery()
                .eq(Project::getTenantId, tenantId)
                .isNull(Project::getDeletedAt));
    }

    private User resolveImpersonateUser(Long tenantId, Long requestedUserId) {
        if (requestedUserId != null) {
            User user = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                    .eq(User::getTenantId, tenantId)
                    .eq(User::getId, requestedUserId)
                    .eq(User::getStatus, 1)
                    .isNull(User::getDeletedAt));
            if (user == null) {
                throw new BizException(ErrorCode.NOT_FOUND, "目标用户不存在或已禁用");
            }
            return user;
        }

        User owner = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getTenantId, tenantId)
                .eq(User::getStatus, 1)
                .isNull(User::getDeletedAt)
                .in(User::getRole, List.of("owner", "admin"))
                .last("LIMIT 1"));
        if (owner != null) {
            return owner;
        }

        User firstActive = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getTenantId, tenantId)
                .eq(User::getStatus, 1)
                .isNull(User::getDeletedAt)
                .orderByAsc(User::getCreatedAt)
                .last("LIMIT 1"));
        if (firstActive == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "租户下没有可模拟登录的活跃用户");
        }
        return firstActive;
    }

    private void validateMemberRequest(TenantMemberRequest request, boolean create) {
        if (request == null) {
            throw new BizException(ErrorCode.INVALID_PARAM, "成员信息不能为空");
        }
        if (create && !StringUtils.hasText(request.getUsername())) {
            throw new BizException(ErrorCode.INVALID_PARAM, "用户名必填");
        }
        if (create && !StringUtils.hasText(request.getEmail())) {
            throw new BizException(ErrorCode.INVALID_PARAM, "邮箱必填");
        }
        if (StringUtils.hasText(request.getPassword()) && request.getPassword().length() < 8) {
            throw new BizException(ErrorCode.INVALID_PARAM, "密码长度不能少于 8 位");
        }
        if (request.getStatus() != null && request.getStatus() != 0 && request.getStatus() != 1) {
            throw new BizException(ErrorCode.INVALID_PARAM, "成员状态必须为 0 或 1");
        }
    }

    private String normalizeMemberRole(String role) {
        String value = StringUtils.hasText(role) ? role.trim().toLowerCase() : "member";
        if (!List.of("owner", "admin", "member").contains(value)) {
            throw new BizException(ErrorCode.INVALID_PARAM, "组织角色必须为 owner/admin/member");
        }
        return value;
    }

    private String defaultPosition(String role) {
        return switch (role == null ? "member" : role) {
            case "owner" -> "企业管理员";
            case "admin" -> "组织管理员";
            default -> "短剧制作";
        };
    }

    private String trimToNull(String value) {
        return StringUtils.hasText(value) ? value.trim() : null;
    }

    private void enforceMemberQuota(Long tenantId) {
        TenantQuota quota = tenantQuotaMapper.selectById(tenantId);
        Integer maxMembers = quota == null ? null : quota.getMaxMembers();
        if (maxMembers != null && maxMembers > 0 && countMembers(tenantId) >= maxMembers) {
            throw new BizException(ErrorCode.INVALID_PARAM, "当前套餐成员数已达上限");
        }
    }

    private User requireTenantUser(Long tenantId, Long userId) {
        User user = userMapper.selectOne(Wrappers.<User>lambdaQuery()
                .eq(User::getTenantId, tenantId)
                .eq(User::getId, userId)
                .isNull(User::getDeletedAt));
        if (user == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "成员不存在");
        }
        return user;
    }

    private long countActiveOwners(Long tenantId) {
        return userMapper.selectCount(Wrappers.<User>lambdaQuery()
                .eq(User::getTenantId, tenantId)
                .eq(User::getRole, "owner")
                .isNull(User::getDeletedAt));
    }

    private TenantMemberRow memberRow(Long tenantId, Long userId) {
        List<TenantMemberRow> rows = jdbcTemplate.query("""
                select tm.id as member_id,
                       u.id as user_id,
                       u.tenant_id,
                       u.username,
                       u.email,
                       u.phone,
                       u.role as account_role,
                       tm.role as member_role,
                       tm.position,
                       u.status,
                       u.last_login_at,
                       tm.joined_at
                  from users u
                  left join tenant_members tm on tm.tenant_id = u.tenant_id and tm.user_id = u.id
                 where u.tenant_id = ?
                   and u.id = ?
                   and u.deleted_at is null
                """, BeanPropertyRowMapper.newInstance(TenantMemberRow.class), tenantId, userId);
        if (rows.isEmpty()) {
            throw new BizException(ErrorCode.NOT_FOUND, "成员不存在");
        }
        return rows.get(0);
    }
}
