package com.ruoyi.ccdi.project.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementCounterpartyVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
import com.ruoyi.ccdi.project.service.ICcdiBankStatementService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 流水明细查询Service实现
 *
 * @author ruoyi
 */
@Service
public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {

    private static final Set<String> ALLOWED_TAB_TYPES = Set.of("all", "in", "out");
    private static final Set<String> ALLOWED_ORDER_DIRECTIONS = Set.of("asc", "desc");
    private static final Set<String> ALLOWED_SUSPICIOUS_TYPES =
        Set.of("ALL", "MODEL_RULE", "EXTERNAL_PERSON", "NAME_LIST");

    @Resource
    private CcdiBankStatementMapper bankStatementMapper;

    @Resource
    private CcdiBankTagResultMapper bankTagResultMapper;

    @Override
    public CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId) {
        CcdiBankStatementFilterOptionsVO options = bankStatementMapper.selectFilterOptions(projectId);
        return options == null ? new CcdiBankStatementFilterOptionsVO() : options;
    }

    @Override
    public Page<CcdiBankStatementListVO> selectStatementPage(Page<CcdiBankStatementListVO> page,
                                                             CcdiBankStatementQueryDTO queryDTO) {
        CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
        normalizeQuery(normalizedQuery);
        Page<CcdiBankStatementListVO> result = bankStatementMapper.selectStatementPage(page, normalizedQuery);
        attachHitTags(result == null ? Collections.emptyList() : result.getRecords(), normalizedQuery.getProjectId());
        return result;
    }

    @Override
    public Page<CcdiBankStatementCounterpartyVO> selectCounterpartyPage(
        Page<CcdiBankStatementCounterpartyVO> page,
        CcdiBankStatementQueryDTO queryDTO
    ) {
        CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
        normalizeQuery(normalizedQuery);
        return bankStatementMapper.selectCounterpartyPage(page, normalizedQuery);
    }

    @Override
    public List<CcdiBankStatementExcel> selectStatementListForExport(CcdiBankStatementQueryDTO queryDTO) {
        CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
        normalizeQuery(normalizedQuery);
        List<CcdiBankStatementListVO> rows = bankStatementMapper.selectStatementListForExport(normalizedQuery);
        if (rows == null || rows.isEmpty()) {
            return Collections.emptyList();
        }
        attachHitTags(rows, normalizedQuery.getProjectId());
        return rows.stream().map(this::toExcel).collect(Collectors.toList());
    }

    @Override
    public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) {
        return getStatementDetail(bankStatementId, null, null);
    }

    @Override
    public CcdiBankStatementDetailVO getStatementDetail(
        Long bankStatementId,
        String modelCode,
        String suspiciousType
    ) {
        CcdiBankStatementDetailVO detail = bankStatementMapper.selectStatementDetailById(bankStatementId);
        if (detail == null || detail.getProjectId() == null || detail.getBankStatementId() == null) {
            return detail;
        }
        Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
            detail.getProjectId(),
            List.of(detail.getBankStatementId()),
            normalizeUpperCase(modelCode),
            normalizeSuspiciousType(suspiciousType)
        );
        detail.setHitTags(new ArrayList<>(hitTagMap.getOrDefault(detail.getBankStatementId(), Collections.emptyList())));
        return detail;
    }

    private void attachHitTags(List<CcdiBankStatementListVO> rows, Long projectId) {
        if (rows == null || rows.isEmpty() || projectId == null) {
            return;
        }
        List<Long> bankStatementIds = rows.stream()
                .map(CcdiBankStatementListVO::getBankStatementId)
                .filter(item -> item != null)
                .distinct()
                .collect(Collectors.toList());
        if (bankStatementIds.isEmpty()) {
            return;
        }
        Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
            projectId,
            bankStatementIds,
            null,
            null
        );
        rows.forEach(row -> row.setHitTags(new ArrayList<>(
                hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
        )));
    }

    private Map<Long, List<CcdiBankStatementHitTagVO>> loadHitTagMap(
        Long projectId,
        List<Long> bankStatementIds,
        String modelCode,
        String suspiciousType
    ) {
        if (projectId == null || bankStatementIds == null || bankStatementIds.isEmpty()) {
            return Collections.emptyMap();
        }
        List<CcdiBankStatementHitTagVO> hitTags =
            bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(
                projectId,
                bankStatementIds,
                modelCode,
                suspiciousType
            );
        if (hitTags == null || hitTags.isEmpty()) {
            return Collections.emptyMap();
        }
        return hitTags.stream()
                .filter(item -> item.getBankStatementId() != null)
                .collect(Collectors.groupingBy(
                        CcdiBankStatementHitTagVO::getBankStatementId,
                        LinkedHashMap::new,
                        Collectors.toList()
                ));
    }

    private void normalizeQuery(CcdiBankStatementQueryDTO queryDTO) {
        queryDTO.setTransactionStartTime(normalizeText(queryDTO.getTransactionStartTime()));
        queryDTO.setTransactionEndTime(normalizeText(queryDTO.getTransactionEndTime()));
        queryDTO.setCounterpartyName(normalizeText(queryDTO.getCounterpartyName()));
        queryDTO.setUserMemo(normalizeText(queryDTO.getUserMemo()));
        queryDTO.setCounterpartyAccount(normalizeText(queryDTO.getCounterpartyAccount()));
        queryDTO.setTransactionType(normalizeText(queryDTO.getTransactionType()));
        queryDTO.setOurSubjects(normalizeList(queryDTO.getOurSubjects()));
        queryDTO.setOurCertNos(normalizeList(queryDTO.getOurCertNos()));
        queryDTO.setOurBanks(normalizeList(queryDTO.getOurBanks()));
        queryDTO.setOurAccounts(normalizeList(queryDTO.getOurAccounts()));
        queryDTO.setCounterpartyNameEmpty(Boolean.TRUE.equals(queryDTO.getCounterpartyNameEmpty()));
        queryDTO.setUserMemoEmpty(Boolean.TRUE.equals(queryDTO.getUserMemoEmpty()));
        queryDTO.setCounterpartyAccountEmpty(Boolean.TRUE.equals(queryDTO.getCounterpartyAccountEmpty()));
        queryDTO.setTransactionTypeEmpty(Boolean.TRUE.equals(queryDTO.getTransactionTypeEmpty()));
        queryDTO.setTabType(normalizeTabType(queryDTO.getTabType()));
        queryDTO.setOrderBy(normalizeOrderBy(queryDTO.getOrderBy()));
        queryDTO.setOrderDirection(normalizeOrderDirection(queryDTO.getOrderDirection()));
    }

    private String normalizeTabType(String tabType) {
        String normalized = normalizeLowerCase(tabType);
        return normalized != null && ALLOWED_TAB_TYPES.contains(normalized) ? normalized : "all";
    }

    private String normalizeOrderBy(String orderBy) {
        String normalized = normalizeText(orderBy);
        if (normalized == null) {
            return "trxDate";
        }
        if ("amount".equalsIgnoreCase(normalized)) {
            return "amount";
        }
        if ("trxDate".equalsIgnoreCase(normalized)) {
            return "trxDate";
        }
        if ("latestTrxDate".equalsIgnoreCase(normalized)) {
            return "latestTrxDate";
        }
        if ("inAmount".equalsIgnoreCase(normalized)) {
            return "inAmount";
        }
        if ("outAmount".equalsIgnoreCase(normalized)) {
            return "outAmount";
        }
        if ("totalAmount".equalsIgnoreCase(normalized)) {
            return "totalAmount";
        }
        if ("totalCount".equalsIgnoreCase(normalized)) {
            return "totalCount";
        }
        return "trxDate";
    }

    private String normalizeOrderDirection(String orderDirection) {
        String normalized = normalizeLowerCase(orderDirection);
        return normalized != null && ALLOWED_ORDER_DIRECTIONS.contains(normalized) ? normalized : "desc";
    }

    private String normalizeLowerCase(String value) {
        String normalized = normalizeText(value);
        return normalized == null ? null : normalized.toLowerCase(Locale.ROOT);
    }

    private String normalizeUpperCase(String value) {
        String normalized = normalizeText(value);
        return normalized == null ? null : normalized.toUpperCase(Locale.ROOT);
    }

    private String normalizeSuspiciousType(String suspiciousType) {
        String normalized = normalizeUpperCase(suspiciousType);
        return normalized != null && ALLOWED_SUSPICIOUS_TYPES.contains(normalized) ? normalized : null;
    }

    private String normalizeText(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    private List<String> normalizeList(List<String> values) {
        if (values == null || values.isEmpty()) {
            return null;
        }
        List<String> normalized = values.stream()
            .map(this::normalizeText)
            .filter(item -> item != null)
            .distinct()
            .collect(Collectors.toList());
        return normalized.isEmpty() ? null : normalized;
    }

    private CcdiBankStatementExcel toExcel(CcdiBankStatementListVO row) {
        CcdiBankStatementExcel excel = new CcdiBankStatementExcel();
        excel.setTrxDate(row.getTrxDate());
        excel.setLeAccountNo(row.getLeAccountNo());
        excel.setLeAccountName(row.getLeAccountName());
        excel.setCustomerAccountName(row.getCustomerAccountName());
        excel.setCustomerAccountNo(row.getCustomerAccountNo());
        excel.setUserMemo(row.getUserMemo());
        excel.setCashType(row.getCashType());
        excel.setHitTags(formatHitTags(row.getHitTags()));
        excel.setDisplayAmount(row.getDisplayAmount());
        return excel;
    }

    private String formatHitTags(List<CcdiBankStatementHitTagVO> hitTags) {
        if (hitTags == null || hitTags.isEmpty()) {
            return "";
        }
        return hitTags.stream()
                .map(CcdiBankStatementHitTagVO::getRuleName)
                .filter(item -> item != null && !item.isBlank())
                .distinct()
                .collect(Collectors.joining("、"));
    }
}
