UserApplicationService.java

package com.yumu.noveltranslator.application.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yumu.noveltranslator.config.tenant.TenantContext;
import com.yumu.noveltranslator.port.dto.entity.TranslationHistoryResponse;
import com.yumu.noveltranslator.port.dto.entity.UserStatisticsResponse;
import com.yumu.noveltranslator.port.dto.entity.UserQuotaResponse;
import com.yumu.noveltranslator.port.dto.entity.UserPreferencesResponse;
import com.yumu.noveltranslator.port.dto.entity.UserPreferencesRequest;
import com.yumu.noveltranslator.port.dto.entity.PlatformStatsResponse;
import com.yumu.noveltranslator.port.dto.translation.GlossaryResponse;
import com.yumu.noveltranslator.port.dto.translation.GlossaryItemRequest;
import com.yumu.noveltranslator.port.dto.common.PageResponse;
import com.yumu.noveltranslator.domain.model.Glossary;
import com.yumu.noveltranslator.domain.model.User;
import com.yumu.noveltranslator.domain.model.UserPreference;
import com.yumu.noveltranslator.enums.ErrorCodeEnum;
import com.yumu.noveltranslator.port.out.GlossaryRepositoryPort;
import com.yumu.noveltranslator.port.out.TranslationRepositoryPort;
import com.yumu.noveltranslator.port.out.UserRepositoryPort;
import com.yumu.noveltranslator.properties.TranslationLimitProperties;
import com.yumu.noveltranslator.domain.service.QuotaService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户信息服务:个人资料、术语库、偏好设置、统计数据
 */
@Service
@RequiredArgsConstructor
public class UserApplicationService implements com.yumu.noveltranslator.port.in.UserPort {

    private final UserRepositoryPort userPort;
    private final TranslationRepositoryPort translationPort;
    private final GlossaryRepositoryPort glossaryPort;
    private final TranslationLimitProperties limitProperties;
    private final QuotaService quotaService;
    private final com.yumu.noveltranslator.port.in.TranslationTaskPort translationTaskPort;

    /**
     * 更新用户信息
     */
    public void updateUser(User user) {
        userPort.update(user);
    }

    /**
     * 获取用户统计数据
     */
    public UserStatisticsResponse getUserStatistics(Long userId) {
        UserStatisticsResponse response = new UserStatisticsResponse();

        int totalCount = translationPort.countHistoryByUserId(userId);
        response.setTotalTranslations(totalCount);

        int textCount = translationPort.countHistoryByUserIdAndType(userId, "text");
        int docCount = translationPort.countHistoryByUserIdAndType(userId, "document");
        response.setTextTranslations(textCount);
        response.setDocumentTranslations(docCount);

        Long totalChars = translationPort.sumHistorySourceTextLengthByUserId(userId);
        response.setTotalCharacters(totalChars != null ? totalChars : 0L);
        response.setTotalDocuments(docCount);

        LocalDateTime weekAgo = LocalDateTime.now().minusWeeks(1);
        LocalDateTime monthAgo = LocalDateTime.now().minusMonths(1);
        response.setWeekTranslations(translationPort.countHistoryByUserIdAfter(userId, weekAgo));
        response.setMonthTranslations(translationPort.countHistoryByUserIdAfter(userId, monthAgo));

        return response;
    }

    /**
     * 获取用户配额信息
     */
    public UserQuotaResponse getUserQuota(User user) {
        UserQuotaResponse response = new UserQuotaResponse();
        String level = user.getUserLevel() != null ? user.getUserLevel() : "free";
        response.setUserLevel(level.toUpperCase());

        long monthlyChars = quotaService.getMonthlyQuota(level);
        long usedThisMonth = quotaService.getUsedThisMonth(user.getId());
        long remaining = Math.max(0, monthlyChars - usedThisMonth);

        response.setMonthlyChars(monthlyChars);
        response.setUsedThisMonth(usedThisMonth);
        response.setRemainingChars(remaining);

        int concurrency;
        if ("max".equalsIgnoreCase(level)) {
            concurrency = limitProperties.getMaxConcurrencyLimit();
        } else if ("pro".equalsIgnoreCase(level)) {
            concurrency = limitProperties.getProConcurrencyLimit();
        } else {
            concurrency = limitProperties.getFreeConcurrencyLimit();
        }
        response.setConcurrencyLimit(concurrency);

        double fastMult = limitProperties.getFastModeMultiplier();
        double expertMult = limitProperties.getExpertModeMultiplier();
        double teamMult = limitProperties.getTeamModeMultiplier();
        response.setFastModeEquivalent((long) (remaining / fastMult));
        response.setExpertModeEquivalent((long) (remaining / expertMult));
        response.setTeamModeEquivalent((long) (remaining / teamMult));

        return response;
    }

    /**
     * 获取术语库列表
     */
    public PageResponse<GlossaryResponse> getGlossaryList(Long userId, int page, int pageSize, String search) {
        var glossaryPage = glossaryPort.findGlossaryPaged(userId, search, page, pageSize);

        List<GlossaryResponse> list = glossaryPage.getRecords().stream()
                .map(this::toGlossaryResponse)
                .collect(Collectors.toList());

        return PageResponse.of(page, pageSize, glossaryPage.getTotal(), list);
    }

    /**
     * 获取术语库详情
     */
    public GlossaryResponse getGlossaryDetail(Long userId, Long glossaryId) {
        return glossaryPort.findGlossaryById(glossaryId)
                .filter(g -> g.getDeleted() == null || g.getDeleted() != 1)
                .filter(g -> g.getUserId().equals(userId))
                .map(this::toGlossaryResponse)
                .orElse(null);
    }

    /**
     * 获取术语列表
     */
    public List<GlossaryResponse> getGlossaryTerms(Long userId) {
        return fetchAllGlossaries(userId);
    }

    private List<GlossaryResponse> fetchAllGlossaries(Long userId) {
        return glossaryPort.findGlossaryByUserId(userId).stream()
                .map(this::toGlossaryResponse)
                .collect(Collectors.toList());
    }

    /**
     * 创建术语项
     */
    public GlossaryResponse createGlossaryItem(Long userId, GlossaryItemRequest request) {
        Glossary glossary = new Glossary();
        glossary.setUserId(userId);
        glossary.setSourceWord(request.getSourceWord());
        glossary.setTargetWord(request.getTargetWord());
        glossary.setRemark(request.getRemark());

        glossaryPort.saveGlossary(glossary);
        return toGlossaryResponse(glossary);
    }

    /**
     * 更新术语项
     */
    public GlossaryResponse updateGlossaryItem(Long userId, Long glossaryId, GlossaryItemRequest request) {
        return glossaryPort.findGlossaryById(glossaryId)
                .filter(g -> g.getDeleted() == null || g.getDeleted() != 1)
                .filter(g -> g.getUserId().equals(userId))
                .map(glossary -> {
                    if (request.getSourceWord() != null) {
                        glossary.setSourceWord(request.getSourceWord());
                    }
                    if (request.getTargetWord() != null) {
                        glossary.setTargetWord(request.getTargetWord());
                    }
                    if (request.getRemark() != null) {
                        glossary.setRemark(request.getRemark());
                    }
                    glossaryPort.updateGlossary(glossary);
                    return toGlossaryResponse(glossary);
                }).orElse(null);
    }

    /**
     * 删除术语项
     */
    public boolean deleteGlossaryItem(Long userId, Long glossaryId) {
        return glossaryPort.findGlossaryById(glossaryId)
                .filter(g -> g.getDeleted() == null || g.getDeleted() != 1)
                .filter(g -> g.getUserId().equals(userId))
                .map(glossaryPort::deleteGlossary)
                .orElse(false);
    }

    /**
     * 批量导入术语
     */
    public int batchImportGlossaryItems(Long userId, List<GlossaryItemRequest> items) {
        int count = 0;
        for (GlossaryItemRequest item : items) {
            try {
                createGlossaryItem(userId, item);
                count++;
            } catch (Exception e) {
                // 跳过失败的项
            }
        }
        return count;
    }

    /**
     * 获取用户偏好设置
     */
    public UserPreferencesResponse getUserPreferences(Long userId) {
        return userPort.findPreferenceByUserId(userId)
                .map(this::toPreferencesResponse)
                .orElseGet(this::buildDefaultPreferences);
    }

    /**
     * 更新用户偏好设置
     */
    public UserPreferencesResponse updateUserPreferences(Long userId, UserPreferencesRequest request) {
        return userPort.findPreferenceByUserId(userId).map(pref -> {
            if (request.getDefaultEngine() != null) pref.setDefaultEngine(request.getDefaultEngine());
            if (request.getDefaultTargetLang() != null) pref.setDefaultTargetLang(request.getDefaultTargetLang());
            if (request.getEnableGlossary() != null) pref.setEnableGlossary(request.getEnableGlossary());
            if (request.getDefaultGlossaryId() != null) pref.setDefaultGlossaryId(request.getDefaultGlossaryId());
            if (request.getEnableCache() != null) pref.setEnableCache(request.getEnableCache());
            if (request.getAutoTranslateSelection() != null) pref.setAutoTranslateSelection(request.getAutoTranslateSelection());
            if (request.getFontSize() != null) pref.setFontSize(request.getFontSize());
            if (request.getThemeMode() != null) pref.setThemeMode(request.getThemeMode());
            userPort.updatePreference(pref);
            return toPreferencesResponse(pref);
        }).orElseGet(() -> {
            UserPreference pref = new UserPreference();
            pref.setUserId(userId);
            pref.setDefaultEngine(request.getDefaultEngine() != null ? request.getDefaultEngine() : "google");
            pref.setDefaultTargetLang(request.getDefaultTargetLang() != null ? request.getDefaultTargetLang() : "zh");
            pref.setEnableGlossary(request.getEnableGlossary() != null ? request.getEnableGlossary() : true);
            pref.setDefaultGlossaryId(request.getDefaultGlossaryId());
            pref.setEnableCache(request.getEnableCache() != null ? request.getEnableCache() : true);
            pref.setAutoTranslateSelection(request.getAutoTranslateSelection() != null ? request.getAutoTranslateSelection() : true);
            pref.setFontSize(request.getFontSize() != null ? request.getFontSize() : 14);
            pref.setThemeMode(request.getThemeMode() != null ? request.getThemeMode() : "light");
            userPort.savePreference(pref);
            return toPreferencesResponse(pref);
        });
    }

    /**
     * 获取平台统计信息(跨租户全局统计)
     */
    public PlatformStatsResponse getPlatformStats() {
        PlatformStatsResponse response = new PlatformStatsResponse();

        try {
            TenantContext.setBypassTenant(true);

            response.setTotalUsers(userPort.countActiveUsers());

            LocalDateTime weekAgo = LocalDateTime.now().minusWeeks(1);
            LocalDateTime monthAgo = LocalDateTime.now().minusMonths(1);
            LocalDateTime todayStart = LocalDate.now().atStartOfDay();
            response.setActiveUsersWeek(translationPort.countActiveUsersAfter(weekAgo));
            response.setActiveUsersMonth(translationPort.countActiveUsersAfter(monthAgo));
            response.setActiveUsersToday(translationPort.countActiveUsersAfter(todayStart));

            response.setTotalTranslations(translationPort.countAllHistory());
            response.setTranslationsToday(translationPort.countHistoryAfter(todayStart));
            response.setTotalCharacters(translationPort.sumAllHistorySourceTextLength());
            response.setTotalDocumentTranslations(translationPort.countDocumentTranslations());
            response.setTotalGlossaries(glossaryPort.countAllGlossaries());

            response.setSystemStatus("normal");
        } finally {
            TenantContext.setBypassTenant(false);
        }

        return response;
    }

    /**
     * 获取用户翻译历史
     */
    public PageResponse<TranslationHistoryResponse> getTranslationHistory(Long userId, int page, int pageSize, String type) {
        var histories = translationTaskPort.getTranslationHistory(userId, page, pageSize, type);
        int total = translationTaskPort.countTranslationHistory(userId, type);

        var responseList = histories.stream()
                .map(translationTaskPort::toHistoryResponse)
                .toList();

        return PageResponse.of(page, pageSize, (long) total, responseList);
    }

    private UserPreferencesResponse buildDefaultPreferences() {
        UserPreferencesResponse response = new UserPreferencesResponse();
        response.setDefaultEngine("google");
        response.setDefaultTargetLang("zh");
        response.setEnableGlossary(true);
        response.setDefaultGlossaryId(null);
        response.setEnableCache(true);
        response.setAutoTranslateSelection(true);
        response.setFontSize(14);
        response.setThemeMode("light");
        return response;
    }

    private UserPreferencesResponse toPreferencesResponse(UserPreference pref) {
        UserPreferencesResponse response = new UserPreferencesResponse();
        response.setDefaultEngine(pref.getDefaultEngine());
        response.setDefaultTargetLang(pref.getDefaultTargetLang());
        response.setEnableGlossary(pref.getEnableGlossary());
        response.setDefaultGlossaryId(pref.getDefaultGlossaryId());
        response.setEnableCache(pref.getEnableCache());
        response.setAutoTranslateSelection(pref.getAutoTranslateSelection());
        response.setFontSize(pref.getFontSize());
        response.setThemeMode(pref.getThemeMode());
        return response;
    }

    private GlossaryResponse toGlossaryResponse(Glossary glossary) {
        GlossaryResponse response = new GlossaryResponse();
        response.setId(glossary.getId());
        response.setSourceWord(glossary.getSourceWord());
        response.setTargetWord(glossary.getTargetWord());
        response.setRemark(glossary.getRemark());
        response.setCreateTime(glossary.getCreateTime());
        return response;
    }
}