package com.yaoai.admin.tenant;

import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.SaLoginModel;
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.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.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.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.TenantMapper;
import com.yaoai.domain.mapper.TenantQuotaMapper;
import com.yaoai.domain.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
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.List;

@Service
@RequiredArgsConstructor
public class TenantAdminService {

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

    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 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<User> members(Long tenantId) {
        requireTenant(tenantId);
        return userMapper.findByTenant(tenantId);
    }

    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;
    }
}
