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

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import com.ecep.contract.constant.CompanyCustomerConstant;
import com.ecep.contract.util.CompanyUtils;
import com.ecep.contract.util.FileUtils;
import org.hibernate.Hibernate;
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.stereotype.Service;
import org.springframework.util.StringUtils;

import com.ecep.contract.CompanyCustomerFileType;
import com.ecep.contract.IEntityService;
import com.ecep.contract.ds.company.CompanyFileUtils;
import com.ecep.contract.ds.company.service.CompanyBasicService;
import com.ecep.contract.ds.customer.repository.CompanyCustomerEvaluationFormFileRepository;
import com.ecep.contract.ds.customer.repository.CompanyCustomerRepository;
import com.ecep.contract.ds.customer.repository.CustomerCatalogRepository;
import com.ecep.contract.ds.other.service.SysConfService;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyBasicFile;
import com.ecep.contract.model.CompanyCustomer;
import com.ecep.contract.model.CompanyCustomerEntity;
import com.ecep.contract.model.CompanyCustomerEvaluationFormFile;
import com.ecep.contract.model.CompanyCustomerFile;
import com.ecep.contract.model.CustomerCatalog;
import com.ecep.contract.util.MyStringUtils;
import com.ecep.contract.util.SpecificationUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import jakarta.persistence.criteria.Path;

@Lazy
@Service
@CacheConfig(cacheNames = "company-customer")
public class CompanyCustomerService extends CompanyBasicService
        implements IEntityService<CompanyCustomer> {
    private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerService.class);

    @Lazy
    @Autowired
    private CompanyCustomerRepository companyCustomerRepository;
    @Lazy
    @Autowired
    private CompanyCustomerFileService companyCustomerFileService;
    @Lazy
    @Autowired
    private CompanyCustomerEvaluationFormFileRepository companyCustomerEvaluationFormFileRepository;
    @Lazy
    @Autowired
    private SysConfService confService;
    @Lazy
    @Autowired
    private CustomerCatalogRepository customerCatalogRepository;
    @Lazy
    @Autowired
    private CompanyCustomerEntityService companyCustomerEntityService;

    public CompanyCustomer findByCompany(Company company) {
        return companyCustomerRepository.findByCompany(company).orElse(null);
    }

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

    @Caching(evict = {
            @CacheEvict(key = "#p0.id")
    })
    public CompanyCustomer save(CompanyCustomer companyCustomer) {
        return companyCustomerRepository.save(companyCustomer);
    }

    @Caching(evict = {
            @CacheEvict(key = "#p0")
    })
    @Override
    public void delete(CompanyCustomer entity) {
        companyCustomerRepository.delete(entity);
    }

    @Override
    public Specification<CompanyCustomer> getSpecification(String searchText) {
        if (!StringUtils.hasText(searchText)) {
            return null;
        }

        Specification<CompanyCustomer> nameSpec = (root, query, builder) -> {
            Path<Company> company = root.get("company");
            return companyService.buildSearchPredicate(searchText, company, query, builder);
        };
        // 判断是否全是数字
        if (MyStringUtils.isAllDigit(searchText)) {
            // 全数字就按 Id 查询
            try {
                int id = Integer.parseInt(searchText);
                Specification<CompanyCustomer> idSpec = (root, query, builder) -> {
                    return builder.equal(root.get("id"), id);
                };
                nameSpec = nameSpec.or(idSpec);
            } catch (Exception ignored) {
            }
        }
        List<CompanyCustomerEntity> searched = companyCustomerEntityService.search(searchText);
        if (!searched.isEmpty()) {
            nameSpec = SpecificationUtils.or(nameSpec, (root, query, builder) -> {
                return builder.in(root.get("id")).value(searched.stream()
                        .map(CompanyCustomerEntity::getCustomer)
                        .filter(Objects::nonNull)
                        .map(CompanyCustomer::getId)
                        .collect(Collectors.toSet()));
            });
        }
        return nameSpec;
    }

    @Override
    public List<CompanyCustomer> search(String searchText) {
        Specification<CompanyCustomer> spec = getSpecification(searchText);
        return companyCustomerRepository.findAll(spec, Pageable.ofSize(10)).getContent();
    }

    @Override
    public <T, F extends CompanyBasicFile<T>, ID> void deleteFile(F file) {
        CompanyCustomerFile customerFile = (CompanyCustomerFile) file;
        companyCustomerFileService.delete(customerFile);
    }

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

    /**
     * 重置 客户文件
     *
     * @param companyCustomer 客户对象
     * @param status          输出
     */
    public boolean reBuildingFiles(CompanyCustomer companyCustomer, Consumer<String> status) {
        List<CompanyCustomerFile> dbFiles = companyCustomerFileService.findAllByCustomer(companyCustomer);
        Map<String, CompanyCustomerFile> map = new HashMap<>();

        boolean modified = fetchDbFiles(dbFiles, map, status);

        // 客户目录下
        List<CompanyCustomerFile> retrieveFiles = new ArrayList<>();
        List<File> needMoveToCompanyPath = new ArrayList<>();

        // TODO 客户有曾用名，可能存在多个目录
        fetchFiles(companyCustomer.getPath(), needMoveToCompanyPath, retrieveFiles, map, status);

        // 移动文件到公司目录下 to company path
        moveFileToCompany(companyCustomer.getCompany(), needMoveToCompanyPath);

        status.accept("导入 " + retrieveFiles.size() + " 个文件");

        if (!retrieveFiles.isEmpty()) {
            // update db
            retrieveFiles.forEach(v -> v.setCustomer(companyCustomer));
            companyCustomerFileService.saveAll(retrieveFiles);
            modified = true;
        }

        return modified;
    }

    @Override
    protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file,
            Consumer<String> status) {
        dbFile.setType((T) CompanyCustomerFileType.General);
        fillFile(dbFile, file, null, status);
        companyCustomerFileService.save((CompanyCustomerFile) dbFile);
        return true;
    }

    @Override
    protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file,
            List<File> fileList, Consumer<String> status) {
        boolean modified = super.fillFileAsEvaluationFile(customerFile, file, fileList, status);

        if (fileList != null) {
            File jsonFile = null;
            // 文件名
            String fileName = file.getName();
            // 文件名，不含后缀
            String name = StringUtils.stripFilenameExtension(fileName);

            String jsonFileName = name + FileUtils.JSON;
            for (File f : fileList) {
                // 查找存档文件
                if (f.getName().equals(jsonFileName)) {
                    jsonFile = f;
                    break;
                }
            }
            if (jsonFile != null) {
                if (fileList.remove(jsonFile)) {
                    try {
                        updateEvaluationFileByJsonFile(customerFile, jsonFile, status);
                    } catch (IOException e) {
                        if (logger.isErrorEnabled()) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            }
        }
        return modified;
    }

    private <T, F extends CompanyBasicFile<T>> void updateEvaluationFileByJsonFile(F customerFile, File jsonFile,
            Consumer<String> status) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode root = objectMapper.readTree(jsonFile);
        if (!root.isObject()) {
            return;
        }
        ObjectNode data = (ObjectNode) root;
        data.remove("file");
        data.remove("signDate");
        data.remove("valid");
        JsonNode type = data.remove("type");

        CompanyCustomerEvaluationFormFile formFile = companyCustomerFileService
                .findCustomerEvaluationFormFileByCustomerFile((CompanyCustomerFile) customerFile);
        objectMapper.updateValue(formFile, data);
        logger.info("load json data from {}", jsonFile.getName());
        formFile.setCatalog(type.asText());
        companyCustomerEvaluationFormFileRepository.save(formFile);
        // companyCustomerEvaluationFormFileRepository.save(formFile);
        if (jsonFile.delete()) {
            logger.info("delete json file {}", jsonFile.getName());
        }
    }

    @Override
    protected <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList,
            Consumer<String> status) {
        CompanyCustomerFile customerFile = new CompanyCustomerFile();
        customerFile.setType(CompanyCustomerFileType.General);
        if (fillFile(customerFile, file, fileList, status)) {
            return (F) customerFile;
        }
        return null;
    }

    @Override
    protected <T, F extends CompanyBasicFile<T>> boolean setFileTypeAsEvaluationForm(F file) {
        T type = file.getType();
        if (type != CompanyCustomerFileType.EvaluationForm) {
            file.setType((T) CompanyCustomerFileType.EvaluationForm);
            return true;
        }
        return false;
    }

    protected boolean isEvaluationFile(String fileName) {
        return (fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME1)
                || fileName.contains(CompanyCustomerConstant.EVALUATION_FORM_NAME2))
                && (FileUtils.withExtensions(fileName, FileUtils.JPG, FileUtils.JPEG,
                        FileUtils.PDF));
    }

    public boolean makePathAbsent(CompanyCustomer companyCustomer) {
        String path = companyCustomer.getPath();
        if (StringUtils.hasText(path)) {
            File file = new File(path);
            if (file.exists()) {
                return false;
            }
        }

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

    private File makePath(CompanyCustomer companyCustomer) {
        File basePath = getBasePath();
        Company company = companyCustomer.getCompany();
        if (!Hibernate.isInitialized(company)) {
            company = companyService.findById(company.getId());
        }

        String companyName = company.getName();
        String fileName = CompanyUtils.formatCompanyVendorId(companyCustomer.getId()) + "-"
                + CompanyFileUtils.escapeFileName(companyName);

        File dir = new File(basePath, fileName);
        if (!dir.exists()) {
            if (!dir.mkdir()) {
                return null;
            }
        }
        return dir;
    }

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

    /**
     * 将一个公司的客户信息转移到另一个公司，并保存在数据库中
     *
     * @param from 要转移的客户信息所属的公司
     * @param to   要转移到的公司
     */
    public void resetTo(Company from, Company to) {
        // 这里使用Optional对象来处理可能为空的情况
        Optional<CompanyCustomer> fromCustomer = companyCustomerRepository.findByCompany(from);
        if (fromCustomer.isEmpty()) {
            // 无效数据
            return;
        }
        Optional<CompanyCustomer> toCustomer = companyCustomerRepository.findByCompany(to);
        if (toCustomer.isEmpty()) {
            CompanyCustomer customer = fromCustomer.get();
            customer.setCompany(to);
            // 保存更新后的CompanyCustomer对象到数据库
            companyCustomerRepository.save(customer);
            return;
        }

        // 把 fromCustomer 信息合并到 toCustomer
        resetTo(fromCustomer.get(), toCustomer.get());

    }

    /***
     * 合并两个CompanyCustomer对象，并将fromCustomer的信息合并到toCustomer中，并保存到数据库中。
     * 
     * @param from 源客户对象
     * @param to   目标客户对象
     */
    public void resetTo(CompanyCustomer from, CompanyCustomer to) {
        // file
        companyCustomerFileService.resetTo(from, to);
        // entity
        companyCustomerEntityService.resetTo(from, to);
        // 删除源客户对象
        companyCustomerRepository.delete(from);
    }

    /**
     * 删除 company 的 客户
     */
    public void deleteByCompany(Company company) {
        int deleted = companyCustomerRepository.deleteAllByCompany(company);
        if (deleted > 0) {
            if (logger.isInfoEnabled()) {
                logger.info("Delete {} records by company:#{}", deleted, company.getId());
            }
        }
    }

    @Cacheable(key = "'catalog-'+#p0")
    public CustomerCatalog findCatalogById(Integer id) {
        return customerCatalogRepository.findById(id).orElse(null);
    }

    public List<CustomerCatalog> findAllCatalogs() {
        return customerCatalogRepository.findAll();
    }

    @Cacheable(key = "'catalog-code-'+#p0")
    public CustomerCatalog findCatalogByCode(String code) {
        return customerCatalogRepository.findByCode(code).orElse(null);
    }

    @Caching(evict = {
            @CacheEvict(key = "'catalog-'+#p0.id"),
            @CacheEvict(key = "'catalog-code-'+#p0.code")
    })
    public CustomerCatalog save(CustomerCatalog catalog) {
        return customerCatalogRepository.save(catalog);
    }
}
