package com.ecep.contract.ds.company.service;

import java.io.File;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.ecep.contract.CompanyFileType;
import com.ecep.contract.IEntityService;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.QueryService;
import com.ecep.contract.SpringApp;
import com.ecep.contract.cloud.rk.CloudRkService;
import com.ecep.contract.cloud.tyc.CloudTycService;
import com.ecep.contract.constant.CloudServiceConstant;
import com.ecep.contract.ds.company.CompanyFileUtils;
import com.ecep.contract.ds.company.repository.CompanyFileRepository;
import com.ecep.contract.ds.company.repository.CompanyFileTypeLocalRepository;
import com.ecep.contract.ds.company.repository.CompanyOldNameRepository;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyFile;
import com.ecep.contract.model.CompanyFileTypeLocal;
import com.ecep.contract.model.CompanyOldName;
import com.ecep.contract.model.Contract;
import com.ecep.contract.util.SpecificationUtils;
import com.fasterxml.jackson.databind.JsonNode;

/**
 * 公司文件服务
 */
@Lazy
@Service
@CacheConfig(cacheNames = "company-file")
public class CompanyFileService implements IEntityService<CompanyFile>, QueryService<CompanyFile> {
    public final static String ENTERPRISE_REPORT = "企业信用报告";
    public final static String BUSINESS_LICENSE = "营业执照";
    public final static String ORGANIZATION_CODE_CERTIFICATE = "组织机构代码证";
    public final static String OPERATION_CERTIFICATE = "操作证";
    public final static String PERMIT_CERTIFICATE = "许可证";
    public final static String REGISTRATION_CERTIFICATE = "登记证";
    private static final Logger logger = LoggerFactory.getLogger(CompanyFileService.class);
    @Lazy
    @Autowired
    private CompanyFileRepository companyFileRepository;
    @Lazy
    @Autowired
    private CompanyOldNameRepository companyOldNameRepository;
    @Lazy
    @Autowired
    private CompanyFileTypeLocalRepository fileTypeLocalRepository;

    @Cacheable(key = "#p0")
    public CompanyFile findById(Integer id) {
        return companyFileRepository.findById(id).orElse(null);
    }

    @Override
    public Page<CompanyFile> findAll(Specification<CompanyFile> spec, Pageable pageable) {
        return companyFileRepository.findAll(spec, pageable);
    }

    public List<CompanyFile> findFileByCompanyAndType(Company company, CompanyFileType type) {
        return companyFileRepository.findByCompanyAndType(company, type);
    }

    @Override
    public Specification<CompanyFile> getSpecification(String searchText) {
        if (!StringUtils.hasText(searchText)) {
            return null;
        }
        return (root, query, builder) -> {
            return builder.or(builder.like(root.get("filePath"), "%" + searchText + "%"));
        };
    }

    @Cacheable(key = "'type-locals-'+#p0")
    public Map<CompanyFileType, CompanyFileTypeLocal> findAllFileTypes(String lang) {
        return fileTypeLocalRepository.getCompleteMapByLocal(lang);
    }
    // public List<CompanyFileTypeLocal> findAllFileTypes(String lang) {
    // Map<CompanyFileType, CompanyFileTypeLocal> map =
    // fileTypeLocalRepository.getCompleteMapByLocal(lang);
    // public List<CompanyFileTypeLocal> list = new ArrayList<>(map.values());
    // list.sort((o1, o2) -> Objects.compare(o1.getValue(), o2.getValue(),
    // String::compareTo));
    // return list;
    // }

    @Override
    public Page<CompanyFile> findAll(JsonNode paramsNode, Pageable pageable) {
        Specification<CompanyFile> spec = null;
        if (paramsNode.has("searchText")) {
            spec = getSpecification(paramsNode.get("searchText").asText());
        }
        // field
        spec = SpecificationUtils.andParam(spec, paramsNode, "company");
        return findAll(spec, pageable);
    }

    /**
     * 根据公司的合同的资信报告文件，推算下一个咨询报告日期
     *
     * @param company 公司
     * @param state   状态输出
     * @return 下一个咨询报告日期
     */
    public LocalDate getNextCreditReportDate(Company company, Consumer<String> state) {
        // 检索全部合同
        ContractService contractService = SpringApp.getBean(ContractService.class);
        List<Contract> contractList = contractService.findAllByCompany(company);
        if (contractList.isEmpty()) {
            state.accept("未发现已登记的合同");
            return null;
        }

        LocalDate minDate = LocalDate.of(2023, 1, 1);
        List<Contract> contracts = contractList.stream()
                .filter(v -> !v.getSetupDate().isBefore(minDate))
                .sorted(Comparator.comparing(Contract::getSetupDate))
                .toList();
        if (contracts.isEmpty()) {
            state.accept("没有发现待处理的合同");
            return null;
        }
        // 检索资信报告
        List<CompanyFile> files = findFileByCompanyAndType(company, CompanyFileType.CreditReport);
        if (files.isEmpty()) {
            Contract first = contracts.getFirst();
            // 没有资信报告，返回第一个合同的提交日期
            state.accept("没有资信报告，推荐使用第一个合同 " + first.getCode() + " 的日期 " + first.getSetupDate());
            return first.getSetupDate();
        }

        for (Contract contract : contracts) {
            if (files.stream().noneMatch(v -> MyDateTimeUtils.dateValidFilter(contract.getSetupDate(), v.getApplyDate(),
                    v.getExpiringDate(), 1))) {
                state.accept("发现未匹配的合同 " + contract.getCode() + " " + contract.getSetupDate());
                return contract.getSetupDate();
            }
        }

        return null;
    }

    /**
     * 验证企业文件
     * <p>
     * 检查是否有类型为{@link CompanyFileType#CreditReport
     * 资讯评估}的文件，且参数verifyDate在文件的{@link CompanyFile#getApplyDate()}和{@link CompanyFile#getExpiringDate()}
     * ()}指定验证日期内有
     * <p>
     * 2023-01-01后要求有资信评估报告
     *
     * @param company    检查的公司对象
     * @param verifyDate 检查日期
     * @param status     状态输出
     * @see CompanyFile
     * @see CompanyFileType
     */
    public void verify(Company company, LocalDate verifyDate, Consumer<String> status) {
        if (verifyDate.isBefore(LocalDate.of(2023, 1, 1))) {
            // 不检查2023-01-01之前的资信评估报告
            return;
        }

        // 查询公司的资信评估报告
        List<CompanyFile> files = findFileByCompanyAndType(company, CompanyFileType.CreditReport);
        CompanyFile companyFile = files.stream()
                .filter(v -> v.getApplyDate() != null && v.getExpiringDate() != null)
                .filter(v -> MyDateTimeUtils.dateValidFilter(verifyDate, v.getApplyDate(), v.getExpiringDate(), 30))
                .findFirst().orElse(null);
        if (companyFile == null) {
            List<LocalDate> dates = new ArrayList<>();

            files.stream()
                    .filter(v -> v.getApplyDate() != null && !verifyDate.isBefore(v.getApplyDate()))
                    .max(Comparator.comparing(CompanyFile::getApplyDate))
                    .map(CompanyFile::getApplyDate)
                    .ifPresent(dates::add);

            files.stream()
                    .filter(v -> v.getExpiringDate() != null && !verifyDate.isAfter(v.getExpiringDate()))
                    .min(Comparator.comparing(CompanyFile::getApplyDate))
                    .map(CompanyFile::getApplyDate)
                    .ifPresent(dates::add);

            if (dates.isEmpty()) {
                status.accept("未匹配到资信评估报告");
            } else if (dates.size() == 1) {
                status.accept("未匹配到资信评估报告, 最接近日期：" + dates.getFirst());
            } else {
                LocalDate localDate = dates.stream().max(LocalDate::compareTo).orElse(null);
                status.accept("未匹配到资信评估报告, 最接近日期：" + localDate);
            }
        }
    }

    /**
     * 保存企业文件
     *
     * @param companyFile 企业文件
     * @return 保存后的企业文件
     */
    public CompanyFile save(CompanyFile companyFile) {
        return companyFileRepository.save(companyFile);
    }

    public List<CompanyFile> findAll(Specification<CompanyFile> spec, Sort sort) {
        return companyFileRepository.findAll(spec, sort);
    }

    public void deleteById(int id) {
        companyFileRepository.deleteById(id);
    }

    public void delete(CompanyFile file) {
        companyFileRepository.delete(file);
    }

    public List<CompanyFile> findByCompany(Company company) {
        return companyFileRepository.findByCompany(company);
    }

    /**
     * 重置 公司文件
     *
     * @param company 要重置的公司对象
     * @param status  输出
     */
    public boolean reBuildingFiles(Company company, Consumer<String> status) {
        List<CompanyFile> dbFiles = companyFileRepository.findByCompany(company);
        List<CompanyFile> retrieveFiles = new ArrayList<>();
        boolean modfied = false;

        Map<String, CompanyFile> map = new HashMap<>();
        // 排除掉数据库中重复的
        for (CompanyFile dbFile : dbFiles) {
            String filePath = dbFile.getFilePath();
            // 没有文件信息，无效记录，删除
            if (!StringUtils.hasText(filePath)) {
                companyFileRepository.delete(dbFile);
                modfied = true;
                continue;
            }

            // 目录不存在，删除
            File dir = new File(filePath);
            if (!dir.exists()) {
                companyFileRepository.delete(dbFile);
                modfied = true;
                continue;
            }

            CompanyFile old = map.put(filePath, dbFile);
            // 目录有重复删除
            if (old != null) {
                companyFileRepository.delete(old);
                modfied = true;
            }
        }

        Map<String, File> directoryMap = new HashMap<>();

        // 公司目录
        if (StringUtils.hasText(company.getPath())) {
            File dir = new File(company.getPath());
            directoryMap.put(company.getName(), dir);
        }

        // 获取所有曾用名
        for (CompanyOldName companyOldName : companyOldNameRepository.findAllByCompanyId(company.getId())) {
            String path = companyOldName.getPath();
            if (StringUtils.hasText(path)) {
                File dir = new File(path);
                directoryMap.put(companyOldName.getName(), dir);
            }
        }

        for (Map.Entry<String, File> entry : directoryMap.entrySet()) {
            String companyName = entry.getKey();
            File dir = entry.getValue();
            if (!StringUtils.hasText(companyName)) {
                continue;
            }
            if (dir == null || !dir.exists()) {
                continue;
            }

            File[] files = dir.listFiles();
            if (files == null) {
                // 文件系统出错或者没有相关文件
                continue;
            }
            for (File file : files) {
                // 只处理文件
                if (!file.isFile() || CompanyFileUtils.isHiddenFile(file)) {
                    continue;
                }
                String filePath = file.getAbsolutePath();
                if (!map.containsKey(filePath)) {
                    // 未记录
                    CompanyFile filled = fillFileType(file, status);
                    retrieveFiles.add(filled);
                }
            }
        }

        status.accept("导入 " + retrieveFiles.size() + " 个文件");
        if (retrieveFiles.isEmpty()) {
            return modfied;
        }

        // update db
        retrieveFiles.forEach(v -> v.setCompany(company));
        companyFileRepository.saveAll(retrieveFiles);
        return true;
    }

    /**
     * 从文件名生成公司文件对象，文件已经存在公司对应的存储目录下
     *
     * @param file   文件
     * @param status 状态输出
     * @return 公司文件对象
     */
    private CompanyFile fillFileType(File file, Consumer<String> status) {
        String fileName = file.getName();
        CompanyFile companyFile = new CompanyFile();
        companyFile.setType(CompanyFileType.General);
        companyFile.setFilePath(file.getAbsolutePath());
        fillApplyDateAndExpiringDateAbsent(file, companyFile);

        // 天眼查 基础版企业信用报告
        if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_BASIC_REPORT)
                || fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_MAJOR_REPORT)
                || fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
            companyFile.setType(CompanyFileType.CreditReport);
            fillExpiringDateAbsent(companyFile);
            return companyFile;
        }

        // 天眼查 企业信用信息公示报告
        if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_CREDIT_REPORT)) {
            companyFile.setType(CompanyFileType.CreditInfoPublicityReport);
            return companyFile;
        }

        // 集团相关方平台 元素征信 企业征信报告
        if (fileName.contains(CloudServiceConstant.RK_VENDOR_NAME)
                && fileName.contains(CloudRkService.ENTERPRISE_CREDIT_REPORT)) {
            companyFile.setType(CompanyFileType.CreditReport);
            fillExpiringDateAbsent(companyFile);
            return companyFile;
        }

        // 营业执照
        if (fileName.contains(BUSINESS_LICENSE)) {
            companyFile.setType(CompanyFileType.BusinessLicense);
            return companyFile;
        }

        // 其他企业信用报告
        if (fileName.contains(ENTERPRISE_REPORT)) {
            companyFile.setType(CompanyFileType.CreditReport);
            fillExpiringDateAbsent(companyFile);
            return companyFile;
        }
        return companyFile;
    }

    /**
     * 补齐有效期
     */
    private void fillExpiringDateAbsent(CompanyFile file) {
        LocalDate expiringDate = file.getExpiringDate();
        if (expiringDate == null) {
            LocalDate applyDate = file.getApplyDate();
            if (applyDate != null) {
                expiringDate = applyDate.plusYears(1);
                file.setExpiringDate(expiringDate);
            }
        }
    }

    private static void fillApplyDateAndExpiringDateAbsent(File file, CompanyFile companyFile) {
        LocalDate applyDate = companyFile.getApplyDate();
        if (applyDate != null) {
            return;
        }

        String fileName = file.getName();
        Pattern pattern = Pattern.compile(MyDateTimeUtils.REGEX_DATE);
        Matcher matcher = pattern.matcher(fileName);
        while (matcher.find()) {
            // 找到第一个日期，记作起始日期
            String date = matcher.group();
            try {
                LocalDate n = LocalDate.parse(date);
                companyFile.setApplyDate(n);

                // 如果 截至日期未设置，则第二个日期记作截至日期（如有）
                LocalDate expiringDate = companyFile.getExpiringDate();
                if (expiringDate == null) {
                    while (matcher.find()) {
                        date = matcher.group();
                        try {
                            n = LocalDate.parse(date);
                            companyFile.setExpiringDate(n);
                            break;
                        } catch (Exception e) {
                            if (logger.isDebugEnabled()) {
                                logger.debug(
                                        "parse date failure, it used to set ExpiringDate, {} from {} by Regex:{}, @{}",
                                        date, fileName, MyDateTimeUtils.REGEX_DATE, companyFile);
                            }
                        }
                    }
                }
                break;
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("parse date failure, it used to set ApplyDate, {} from {} by Regex:{}, @{}",
                            date, fileName, MyDateTimeUtils.REGEX_DATE, companyFile);
                }
            }
        }

    }

    /**
     * 移动文件到企业目录下
     *
     * @param company 企业对象
     * @param files   要被移动的文件集合，需要从中选择需要的
     * @param status  状态输出
     */
    public boolean retrieveFromDownloadFiles(Company company, File[] files, Consumer<String> status) {
        Map<String, File> map = new HashMap<>();
        File home = new File(company.getPath());
        map.put(company.getName(), home);
        List<CompanyFile> retrieveFiles = new ArrayList<>();

        // 获取所有曾用名
        for (CompanyOldName companyOldName : companyOldNameRepository.findAllByCompanyId(company.getId())) {
            String name = companyOldName.getName();
            if (!StringUtils.hasText(name)) {
                continue;
            }
            File dir = null;
            String path = companyOldName.getPath();
            if (StringUtils.hasText(path)) {
                dir = new File(path);
            }
            map.put(name, dir);
        }

        // 对所有文件进行遍历
        for (int i = 0; i < files.length; i++) {
            File file = files[i];
            // 只处理文件
            if (!file.isFile()) {
                continue;
            }
            String prefix = (i + 1) + "/" + files.length + ":";
            Consumer<String> inner = (str) -> {
                status.accept(prefix + str);
            };

            String fileName = file.getName();
            inner.accept(fileName);
            for (Map.Entry<String, File> entry : map.entrySet()) {
                String companyName = entry.getKey();
                // 必须要包含公司名称否则无法区分
                if (!fileName.contains(companyName)) {
                    continue;
                }

                // 文件存储的目的地目录
                File dir = entry.getValue();
                if (dir == null) {
                    dir = home;
                }

                CompanyFile filled = fillDownloadFileType(company, file, companyName, dir, inner);
                if (filled != null) {
                    retrieveFiles.add(filled);
                }

            }
        }

        status.accept("导入 " + retrieveFiles.size() + " 个文件");
        if (retrieveFiles.isEmpty()) {
            return false;
        }

        // update db
        retrieveFiles.forEach(v -> v.setCompany(company));
        companyFileRepository.saveAll(retrieveFiles);
        return true;
    }

    /**
     * 从文件名生成公司文件对象
     * 文件从下载目录中导入
     *
     * @param company     公司对象
     * @param file        导入的文件对象
     * @param companyName 公司名称
     * @param destDir     目标目录
     * @param status      状态输出
     * @return 生成的公司文件对象，如果无法转换则返回null
     */
    private CompanyFile fillDownloadFileType(Company company, File file, String companyName, File destDir,
            Consumer<String> status) {
        String fileName = file.getName();
        // 天眼查的报告
        // 目前只有 基础版企业信用报告, 企业信用信息公示报告下载保存时的文件名中没有天眼查
        if (CloudTycService.isTycReport(fileName)) {
            CompanyFile companyFile = new CompanyFile();
            companyFile.setType(CompanyFileType.CreditReport);
            fillApplyDateAbsent(file, companyFile);

            String destFileName = fileName;
            // 重命名 基础版企业信用报告
            for (String report : Arrays.asList(
                    CloudServiceConstant.TYC_ENTERPRISE_ANALYSIS_REPORT,
                    CloudServiceConstant.TYC_ENTERPRISE_BASIC_REPORT,
                    CloudServiceConstant.TYC_ENTERPRISE_MAJOR_REPORT)) {

                if (fileName.contains(report)) {
                    LocalDate applyDate = companyFile.getApplyDate();
                    if (applyDate == null) {
                        applyDate = LocalDate.now();
                        companyFile.setApplyDate(applyDate);
                    }
                    String formatted = MyDateTimeUtils.format(applyDate);
                    String extension = StringUtils.getFilenameExtension(fileName);
                    destFileName = String.format("%s_%s_%s_%s.%s",
                            companyName, CloudServiceConstant.TYC_NAME, report, formatted, extension);
                }
            }

            // 重新设置 企业分析报告 未普通文件
            // if (fileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
            // companyFile.setType(General);
            // }

            File dest = new File(destDir, destFileName);
            // 移动文件
            if (!file.renameTo(dest)) {
                // 移动失败时
                status.accept(fileName + " 无法移动到 " + dest.getAbsolutePath());
                return null;
            }

            status.accept(fileName + " 移动到 " + dest.getAbsolutePath());
            companyFile.setFilePath(dest.getAbsolutePath());

            //
            if (companyFile.getExpiringDate() == null) {
                if (companyFile.getApplyDate() != null) {
                    companyFile.setExpiringDate(companyFile.getApplyDate().plusYears(1));
                }
            }
            return companyFile;
        }

        // 企业信用信息公示报告
        if (fileName.contains(CloudServiceConstant.TYC_ENTERPRISE_CREDIT_REPORT)) {
            CompanyFile companyFile = new CompanyFile();
            companyFile.setType(CompanyFileType.CreditInfoPublicityReport);
            fillApplyDateAbsent(file, companyFile);
            File dest = new File(destDir, fileName);

            // 移动文件
            if (!file.renameTo(dest)) {
                if (dest.exists()) {
                    // 尝试删除已经存在的文件
                    if (!dest.delete()) {
                        status.accept("覆盖时，无法删除已存在的文件 " + dest.getAbsolutePath());
                        return null;
                    }
                    if (file.renameTo(dest)) {
                        Optional<CompanyFile> one = companyFileRepository.findOne(((root, query, builder) -> {
                            return builder.and(
                                    builder.equal(root.get("filePath"), dest.getAbsolutePath()),
                                    builder.equal(root.get("company"), company));
                        }));
                        if (one.isPresent()) {
                            companyFile = one.get();
                        }
                    } else {
                        status.accept(fileName + " 无法覆盖到 " + dest.getAbsolutePath());
                        return null;
                    }
                } else {
                    status.accept(fileName + " 无法移动到 " + dest.getAbsolutePath());
                    return null;
                }
            }

            status.accept(fileName + " 移动到 " + dest.getAbsolutePath());
            companyFile.setFilePath(dest.getAbsolutePath());

            return companyFile;
        }
        return null;
    }

    /**
     * 当 ApplyDate 未设置时，尝试使用文件名中包含的日期
     */
    private static void fillApplyDateAbsent(File file, CompanyFile companyFile) {
        LocalDate applyDate = companyFile.getApplyDate();
        if (applyDate != null) {
            return;
        }
        String fileName = file.getName();
        // 从文件名中提取日期
        LocalDate picked = MyDateTimeUtils.pickLocalDate(fileName);
        if (picked != null) {
            companyFile.setApplyDate(picked);
        }
    }

}
