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

import java.io.File;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

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.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.ecep.contract.IEntityService;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.QueryService;
import com.ecep.contract.cloud.rk.CloudRkService;
import com.ecep.contract.cloud.tyc.CloudTycService;
import com.ecep.contract.cloud.u8.YongYouU8Service;
import com.ecep.contract.constant.CompanyCustomerConstant;
import com.ecep.contract.constant.CompanyVendorConstant;
import com.ecep.contract.ds.company.CompanyFileUtils;
import com.ecep.contract.ds.company.repository.CompanyRepository;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.ds.other.service.SysConfService;
import com.ecep.contract.ds.vendor.service.CompanyVendorService;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyCustomer;
import com.ecep.contract.model.CompanyOldName;
import com.ecep.contract.model.CompanyVendor;
import com.ecep.contract.util.MyStringUtils;
import com.ecep.contract.util.SpecificationUtils;
import com.fasterxml.jackson.databind.JsonNode;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.transaction.Transactional;

/**
 * 公司服务
 */
@Lazy
@Service
@CacheConfig(cacheNames = "company")
public class CompanyService implements IEntityService<Company>, QueryService<Company> {
    private static final Logger logger = LoggerFactory.getLogger(CompanyService.class);
    private static final String COMPANY_BASE_PATH = "company.base.path";

    @Lazy
    @Autowired
    private CompanyRepository companyRepository;
    @Lazy
    @Autowired
    private SysConfService confService;
    @Lazy
    @Autowired
    private CompanyFileService companyFileService;
    @Lazy
    @Autowired
    private ContractService contractService;
    @Lazy
    @Autowired
    private CompanyVendorService companyVendorService;
    @Lazy
    @Autowired
    private CompanyCustomerService companyCustomerService;
    @Lazy
    @Autowired
    private CompanyOldNameService companyOldNameService;
    @Lazy
    @Autowired
    private CompanyContactService companyContactService;
    @Lazy
    @Autowired
    private CloudRkService cloudRkService;
    @Lazy
    @Autowired
    private CloudTycService cloudTycService;
    @Lazy
    @Autowired(required = false)
    private YongYouU8Service yongYouU8Service;

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

    @Cacheable(key = "'name-'+#p0")
    public Company findByName(String name) {
        return companyRepository.findFirstByName(name).orElse(null);
    }

    /**
     * 查找名称是 name 的记录
     *
     * @param name 查询的公司名称
     * @return 记录列表
     */
    public List<Company> findAllByName(String name) {
        return companyRepository.findAllByName(name);
    }

    public Page<Company> findAll(Specification<Company> spec, Pageable pageable) {
        return companyRepository.findAll(spec, pageable);
    }

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

    /**
     * 查找
     * 重复的删除
     *
     * @param uniscid 统一社会信用代码
     * @return 公司对象
     */
    public Company findAndRemoveDuplicateCompanyByUniscid(String uniscid) {
        // 根据统一社会信用代码去查询
        List<Company> companies = companyRepository.findAllByUniscid(uniscid);
        if (companies.isEmpty()) {
            return null;
        }
        if (companies.size() == 1) {
            return companies.getFirst();
        } else {
            List<Company> result = removeDuplicatesByUniscid(companies);
            if (!result.isEmpty()) {
                return result.getFirst();
            }
        }
        return null;
    }

    /**
     * 查找公司，根据名称或简称查询
     * <p>
     * 1. 先按照 name 查找Company记录，如果有多个记录只保留一条，保留规则参考 #removeDuplicatesByUniscid 方法
     * 2. 第一步没有匹配到时，根据名称从曾用名中查询
     * 3. 上一步没有匹配到时，根据简称从公司数据库查询匹配简称的公司
     * 4. 上一步没有匹配到时，根据简称从曾用名中查询
     * 根据简称去查询，如果有多个记录只保留一条，保留规则参考 #removeDuplicatesByUniscid 方法
     * 重复的删除
     *
     * @param name    企业名称
     * @param abbName 别名
     * @return 公司对象
     */
    public Company findAndRemoveDuplicateCompanyByNameOrAbbName(String name, String abbName) {
        Company updater = null;
        {
            // 根据公司全名去查询
            List<Company> companies = companyRepository.findAllByName(name);
            if (companies.isEmpty()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("No record match by {}", name);
                }
            } else if (companies.size() == 1) {
                updater = companies.getFirst();
            } else {
                List<Company> result = removeDuplicatesByUniscid(companies);
                if (!result.isEmpty()) {
                    updater = result.getFirst();
                }
            }
        }

        if (updater == null) {
            // 根据公司名称去曾用名中查询
            List<CompanyOldName> oldNames = companyOldNameService.findAllByName(name);
            if (!oldNames.isEmpty()) {
                CompanyOldName oldName = oldNames.getFirst();
                Optional<Company> optional = companyRepository.findById(oldName.getCompanyId());
                if (optional.isPresent()) {
                    updater = optional.get();
                }
            }
        }

        if (updater == null && StringUtils.hasText(abbName) && !Objects.equals(abbName, name)) {
            // 根据公司全面去查询
            List<Company> companies = companyRepository.findAllByShortName(abbName);
            if (!companies.isEmpty()) {
                updater = companies.removeFirst();
            }

            if (updater == null) {
                // 根据公司名称去曾用名中查询
                List<CompanyOldName> oldNames = companyOldNameService.findAllByName(abbName);
                if (!oldNames.isEmpty()) {
                    CompanyOldName oldName = null;
                    Optional<CompanyOldName> optional1 = oldNames.stream().filter(CompanyOldName::getAmbiguity)
                            .findFirst();
                    oldName = optional1.orElseGet(oldNames::getFirst);
                    Optional<Company> optional = companyRepository.findById(oldName.getCompanyId());
                    if (optional.isPresent()) {
                        updater = optional.get();
                    }
                }
            }
        }

        return updater;
    }

    /**
     * 删除 列表中 Uniscid 重复的
     */
    private List<Company> removeDuplicatesByUniscid(List<Company> list) {
        List<Company> result = new ArrayList<>();
        List<Company> removes = new ArrayList<>();
        Set<String> uniqueUniscidSet = new HashSet<>();
        for (Company company : list) {
            // 名称相同后，统一社会信用编号 也相同的话，删除
            if (uniqueUniscidSet.add(company.getUniscid())) {
                // 没有记录过时
                result.add(company);
            } else {
                // 有重复时
                removes.add(company);
            }
        }

        Company updater = result.getFirst();
        // 合并重复的
        for (Company company : removes) {
            try {
                merge(company, updater);
            } catch (Exception e) {
                logger.error("合并 {} -> {} 时发生错误：{}", company.toPrettyString(), updater.toPrettyString(), e.getMessage(),
                        e);
                throw e;
            }
        }
        return result;
    }

    /**
     * 删除
     * <p>
     * 删除前需要把关联数据解除
     * <ul>
     * <li>{@link CompanyVendorService#deleteByCompany(Company)}</li>
     * <li>{@link CompanyCustomerService#deleteByCompany(Company)}</li>
     * <li>{@link CompanyOldNameService#deleteByCompany(Company)}</li>
     * <li>{@link CompanyContactService#deleteByCompany(Company)}</li>
     * <li>{@link ContractService#deleteByCompany(Company)}</li>
     * <li>{@link CompanyContactService#deleteByCompany(Company)}</li>
     * </ul>
     * 或者 把关联数据转移到其他公司
     * <ul>
     * <li>{@link CompanyVendorService#resetTo(Company, Company)}</li>
     * <li>{@link CompanyCustomerService#resetTo(Company, Company)}</li>
     * <li>{@link CompanyOldNameService#resetTo(Company, Company)}</li>
     * <li>{@link CompanyContactService#resetTo(Company, Company)}</li>
     * <li>{@link ContractService#resetTo(Company, Company)}</li>
     * <li>{@link CompanyContactService#resetTo(Company, Company)}</li>
     * </ul>
     * </p>
     *
     * @param company 要删除的公司对象
     */
    @Caching(evict = {
            @CacheEvict(key = "#p0.id"),
            @CacheEvict(key = "'name-'+#p0.name")
    })
    public void delete(Company company) {
        cloudRkService.deleteByCompany(company);
        cloudTycService.deleteByCompany(company);
        yongYouU8Service.deleteByCompany(company);

        companyOldNameService.deleteByCompany(company);
        companyContactService.deleteByCompany(company);
        // 供应商和客户
        companyVendorService.deleteByCompany(company);
        companyCustomerService.deleteByCompany(company);

        contractService.deleteByCompany(company);
        companyContactService.deleteByCompany(company);

        companyRepository.delete(company);
        if (logger.isInfoEnabled()) {
            logger.info("Delete Company {}", company);
        }
    }

    /**
     * 合并 from 到 to
     * <p>
     * 下述关联数据重设绑定到 to
     * <ol>
     * <li>曾用名</li>
     * <li>供应商</li>
     * <li>客户</li>
     * <li>合同</li>
     * <li>公司合同</li>
     * </ol>
     * </p>
     */
    public void merge(Company from, Company to) {
        // cloudRkService.findById(from.getId());
        cloudRkService.resetTo(from, to);
        cloudTycService.resetTo(from, to);
        yongYouU8Service.resetTo(from, to);

        companyOldNameService.resetTo(from, to);
        companyContactService.resetTo(from, to);
        // 供应商和客户
        companyVendorService.resetTo(from, to);
        companyCustomerService.resetTo(from, to);

        contractService.resetTo(from, to);
        companyContactService.resetTo(from, to);
        companyRepository.delete(from);
        if (logger.isInfoEnabled()) {
            logger.info("Merge {} to {}", from, to);
        }
    }

    /**
     * 保存实体对象，异步方法
     *
     * @param company 要保存的实体对象
     * @return 返回异步调用
     */
    public CompletableFuture<Company> asyncSave(Company company) {
        return CompletableFuture.completedFuture(companyRepository.save(company));
    }

    /**
     * 保存实体对象
     *
     * @param company 要保存的实体对象
     * @return 返回异步调用
     */
    @Caching(evict = {
            @CacheEvict(key = "#p0.id"),
            @CacheEvict(key = "'name-'+#p0.name")
    })
    public Company save(Company company) {
        return companyRepository.save(company);
    }

    public long count() {
        return companyRepository.count();
    }

    public long count(Specification<Company> spec) {
        return companyRepository.count(spec);
    }

    @Transactional
    public void findAllWithStream(Consumer<Stream<Company>> consumer) {
        try (Stream<Company> stream = companyRepository.findAllAsStream()) {
            consumer.accept(stream);
        }
    }

    public File getVendorBasePath() {
        return new File(confService.getString(CompanyVendorConstant.KEY_BASE_PATH));
    }

    public File getCustomerBasePath() {
        return new File(confService.getString(CompanyCustomerConstant.KEY_BASE_PATH));
    }

    public File getBasePath() {
        return new File(confService.getString(COMPANY_BASE_PATH));
    }

    /**
     * 创建公司目录，如果公司目录未设置，或者未存在
     *
     * @param company 要创建目录的公司对象
     * @return 是否创建了目录
     */
    public boolean makePathAbsent(Company company) {
        String path = company.getPath();
        if (StringUtils.hasText(path)) {
            File file = new File(path);
            if (file.exists()) {
                return false;
            }
        }

        File dir = makePath(company);
        if (dir == null) {
            return false;
        }
        if (!dir.exists()) {
            return false;
        }
        company.setPath(dir.getAbsolutePath());
        company.setPathExist(true);
        return true;
    }

    /**
     * 创建企业目录
     *
     * @param company 要创建的企业对象
     * @return 目录
     */
    public File makePath(Company company) {
        File basePath = getBasePath();
        if (!basePath.exists()) {
            return null;
        }
        String companyName = company.getName();
        String district = company.getDistrict();
        if (StringUtils.hasText(district)) {
            String parentPrefix = CompanyFileUtils.getParentPrefixByDistrict(district);
            if (parentPrefix != null) {
                File parent = new File(basePath, parentPrefix);
                if (!parent.exists()) {
                    if (!parent.mkdir()) {
                        return null;
                    }
                }
                String fileName = CompanyFileUtils.escapeFileName(companyName);
                File dir = new File(parent, fileName);
                if (!dir.exists()) {
                    if (!dir.mkdir()) {
                        return null;
                    }
                }
                return dir;
            }
        }
        return null;
    }

    /**
     * 移动文件到企业目录下
     *
     * @param company 企业对象
     * @param files   要被移动的文件集合，需要从中选择需要的
     * @param status  状态输出
     */
    public boolean retrieveFromDownloadFiles(Company company, File[] files, Consumer<String> status) {
        //
        boolean companyChanged = makePathAbsent(company);

        if (!StringUtils.hasText(company.getPath())) {
            // fixed 要退出，需要保存
            if (companyChanged) {
                save(company);
            }
            status.accept("存储目录未设置，请检查");
            return false;
        }

        File home = new File(company.getPath());
        if (!home.exists()) {
            // fixed 要退出，需要保存
            if (companyChanged) {
                company = save(company);
            }
            status.accept(company.getPath() + " 不存在，无法访问，请检查或者修改");
            return false;
        }

        boolean retrieved = companyFileService.retrieveFromDownloadFiles(company, files, status);
        if (companyChanged) {
            save(company);
        }
        return retrieved;
    }

    /**
     * 验证企业状态
     *
     * @param company    要验证的公司
     * @param verifyDate 验证日期
     * @param status     状态输出
     */
    public void verifyEnterpriseStatus(Company company, LocalDate verifyDate, Consumer<String> status) {
        // 检查营业状态
        String entStatus = company.getEntStatus();
        if (StringUtils.hasText(entStatus)) {
            if (entStatus.contains("注销")) {
                LocalDate end = company.getOperationPeriodEnd();
                LocalDate begin = company.getOperationPeriodBegin();
                if (begin == null || end == null) {
                    // 注销时间未知，无法判断是否在 verifyDate 之后注销
                    status.accept("营业状态异常：" + entStatus);
                } else {
                    if (!MyDateTimeUtils.dateValidFilter(verifyDate, begin, end, 0)) {
                        status.accept("营业状态异常：" + entStatus);
                    }
                }
            }
        } else {
            status.accept("营业状态异常：未设置");
        }
    }

    @Override
    public Specification<Company> getSpecification(String searchText) {
        if (!StringUtils.hasText(searchText)) {
            return null;
        }
        // 判断是否全是数字
        boolean isAllDigit = MyStringUtils.isAllDigit(searchText);
        if (isAllDigit) {
            return (root, query, builder) -> {
                return builder.or(
                        builder.like(root.get("id").as(String.class), "%" + searchText + "%"),
                        builder.like(root.get("uniscid"), "%" + searchText + "%"));
            };
        }

        Specification<Company> spec = null;
        Set<Integer> idSet = companyOldNameService.search(searchText).stream().map(CompanyOldName::getCompanyId)
                .collect(Collectors.toSet());
        if (!idSet.isEmpty()) {
            spec = SpecificationUtils.or(spec, (root, query, builder) -> {
                return root.get("id").in(idSet);
            });
        }

        List<CompanyVendor> searchedVendors = companyVendorService.search(searchText);
        if (!searchedVendors.isEmpty()) {
            spec = SpecificationUtils.or(spec, (root, query, builder) -> {
                return builder.in(root.get("id")).value(searchedVendors.stream()
                        .map(CompanyVendor::getCompany)
                        .filter(Objects::nonNull)
                        .map(Company::getId)
                        .collect(Collectors.toSet()));
            });
        }

        List<CompanyCustomer> searchedCustomers = companyCustomerService.search(searchText);
        if (!searchedCustomers.isEmpty()) {
            spec = SpecificationUtils.or(spec, (root, query, builder) -> {
                return builder.in(root.get("id")).value(searchedCustomers.stream()
                        .map(CompanyCustomer::getCompany)
                        .filter(Objects::nonNull)
                        .map(Company::getId)
                        .collect(Collectors.toSet()));
            });
        }

        return SpecificationUtils.or(spec, SpecificationUtils.andWith(searchText, this::buildSearchSpecification));
    }

    protected Specification<Company> buildSearchSpecification(String searchText) {
        return (root, query, builder) -> buildSearchPredicate(searchText, root, query, builder);
    }

    public Predicate buildSearchPredicate(String searchText, Path<Company> root, @Nullable CriteriaQuery<?> query,
            CriteriaBuilder builder) {
        return builder.or(
                builder.like(root.get("name"), "%" + searchText + "%"),
                builder.like(root.get("shortName"), "%" + searchText + "%"),
                builder.like(root.get("uniscid"), "%" + searchText + "%"),
                builder.like(root.get("legalRepresentative"), "%" + searchText + "%"),
                builder.like(root.get("regAddr"), "%" + searchText + "%"),
                builder.like(root.get("address"), "%" + searchText + "%"),
                builder.like(root.get("memo"), "%" + searchText + "%"));
    }

    /**
     * 检索企业
     * 企业名称中模糊搜索
     *
     * @param searchText 搜索文本
     * @return 企业列表，返回前10个企业
     */
    public List<Company> search(String searchText) {
        Specification<Company> spec = getSpecification(searchText);
        return companyRepository.findAll(spec, Pageable.ofSize(10)).getContent();
    }

    public Company createNewCompany(String name) {
        Company company = new Company();
        company.setName(name);
        company.setCreated(LocalDate.now());
        return company;
    }

    public List<String> getAllNames(Company company) {
        List<String> list = new ArrayList<>();
        list.add(company.getName());
        companyOldNameService.findAllByCompany(company).forEach(oldName -> {
            // 歧义的曾用名不采用
            if (oldName.getAmbiguity()) {
                return;
            }
            list.add(oldName.getName());
        });
        return list;
    }
}
