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

import java.io.File;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
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.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.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.ecep.contract.ContractPayWay;
import com.ecep.contract.IEntityService;
import com.ecep.contract.QueryService;
import com.ecep.contract.constant.ContractConstant;
import com.ecep.contract.ds.contract.repository.ContractRepository;
import com.ecep.contract.ds.other.service.SysConfService;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyCustomer;
import com.ecep.contract.model.CompanyVendor;
import com.ecep.contract.model.Contract;
import com.ecep.contract.model.ContractCatalog;
import com.ecep.contract.model.ContractGroup;
import com.ecep.contract.model.ContractKind;
import com.ecep.contract.model.ContractType;
import com.ecep.contract.model.Project;
import com.ecep.contract.util.MyStringUtils;
import com.ecep.contract.util.SpecificationUtils;
import com.fasterxml.jackson.databind.JsonNode;

import jakarta.persistence.criteria.Predicate;

/**
 * 合同服务类
 * 继承基础服务并实现视图模型接口
 */
@Lazy
@Service
@CacheConfig(cacheNames = "contract")
public class ContractService implements IEntityService<Contract>, QueryService<Contract> {
    private static final Logger logger = LoggerFactory.getLogger(ContractService.class);
    @Lazy
    @Autowired
    private ContractCatalogService contractCatalogService;
    @Lazy
    @Autowired
    private ContractTypeService contractTypeService;
    @Lazy
    @Autowired
    private ContractGroupService contractGroupService;
    @Lazy
    @Autowired
    private ContractKindService contractKindService;
    @Lazy
    @Autowired
    private ContractFileService contractFileService;
    @Lazy
    @Autowired
    protected ContractRepository contractRepository;
    @Lazy
    @Autowired
    protected SysConfService confService;

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

    public Contract findByName(String name) {
        return contractRepository.findFirstByName(name).orElse(null);
    }

    @Cacheable(key = "'code-'+#p0")
    public Contract findByCode(String code) {
        return contractRepository.findByCode(code).orElse(null);
    }

    public Contract findByGuid(String guid) {
        return contractRepository.findByGuid(guid).orElse(null);
    }

    /**
     * 保存实体对象，异步方法
     */
    @Caching(evict = {
            @CacheEvict(key = "#p0.id"),
            @CacheEvict(key = "'code-'+#p0.code")
    })
    public Contract save(Contract contract) {
        return contractRepository.save(contract);
    }

    @Caching(evict = {
            @CacheEvict(key = "#p0.id"),
            @CacheEvict(key = "'code-'+#p0.code")
    })
    public void delete(Contract contract) {
        contractRepository.delete(contract);
    }

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

    public long count(Specification<Contract> spec) {
        return contractRepository.count(spec);
    }

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

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

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

        // field
        spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "parentCode");

        // relation
        spec = SpecificationUtils.andParam(spec, paramsNode, "group", "kind", "type", "group", "company", "project");

        return findAll(spec, pageable);
    }

    public List<Contract> findAllByCompany(Company company) {
        return contractRepository.findAllByCompanyId(company.getId());
    }

    public List<Contract> findAllByProject(Project project) {
        return contractRepository.findAllByProjectId(project.getId());
    }

    public List<Contract> findAllSalesByProject(Project project) {
        return contractRepository.findAllByProjectIdAndPayWay(project.getId(), ContractPayWay.RECEIVE);
    }

    public Contract findSalesByProject(Project project) {
        return contractRepository.findByProjectIdAndPayWay(project.getId(), ContractPayWay.RECEIVE).orElse(null);
    }

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

    public ContractGroup findGroupById(Integer id) {
        return contractGroupService.findById(id);
    }

    public ContractGroup findGroupByCode(String groupCode) {
        return contractGroupService.findByCode(groupCode);
    }

    public List<ContractGroup> findAllGroups() {
        return contractGroupService.findAll();
    }

    public ContractType findTypeById(Integer id) {
        return contractTypeService.findById(id);
    }

    public ContractType findTypeByCode(String typeCode) {
        return contractTypeService.findByCode(typeCode);
    }

    public ContractKind findKindById(Integer id) {
        return contractKindService.findById(id);
    }

    public ContractKind findKindByName(String kindName) {
        return contractKindService.findByName(kindName);
    }

    /**
     * 返回合同的分类目录
     */
    public File getContractCatalogPath(ContractCatalog catalog, Contract contract) {
        String parent = catalog.getParent();
        File dir = getBasePath();
        if (StringUtils.hasText(parent)) {
            dir = new File(dir, parent);
            if (!dir.exists()) {
                if (!dir.mkdir()) {
                    System.out.println("unable make directory = " + dir.getAbsolutePath());
                    return null;
                }
            }
        }
        dir = new File(dir, catalog.getPath());
        if (!dir.exists()) {
            if (!dir.mkdir()) {
                System.out.println("unable make directory = " + dir.getAbsolutePath());
                return null;
            }
        }

        if (catalog.isUseYear()) {
            String code = contract.getCode();
            String catalogCode = catalog.getCode();
            int length = catalogCode.length();
            String yearCode = code.substring(length, length + 2);
            dir = new File(dir, "20" + yearCode);
            if (!dir.exists()) {
                if (!dir.mkdir()) {
                    System.out.println("unable make directory = " + dir.getAbsolutePath());
                    return null;
                }
            }
        }

        return dir;
    }

    /**
     * 查找合同的分类
     */
    public ContractCatalog findContractCatalogByContract(Contract contract) {
        String code = contract.getCode();
        String catalogCode = null;

        // 第二个字符是数字时,快速处理
        if (Character.isDigit(code.charAt(1))) {
            // 只有一个字母表示合同类型
            catalogCode = code.substring(0, 1);
        } else {
            // 使用正则找出 合同类型
            String regex = "^([A-Z]{1,4})(\\d{4,5})";
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(code);
            if (matcher.find()) {
                catalogCode = matcher.group(1);
                System.out.println("字母部分: " + matcher.group(1));
                System.out.println("数字部分: " + matcher.group(2));
            }
            if (catalogCode == null) {
                if (logger.isInfoEnabled()) {
                    logger.info("contract {} can't find catalog code", code);
                }
                return null;
            }
        }

        return contractCatalogService.findByCode(catalogCode);
    }

    public Contract createNew() {
        Contract contract = new Contract();
        contract.setCreated(LocalDateTime.now());
        return contract;
    }

    @Override
    public Specification<Contract> getSpecification(String searchText) {
        if (!StringUtils.hasText(searchText)) {
            return null;
        }
        return SpecificationUtils.andWith(searchText, this::buildSearchSpecification);
    }

    /**
     * 构建搜索规范
     */
    protected Specification<Contract> buildSearchSpecification(String searchText) {
        return (root, query, builder) -> {
            return builder.or(
                    builder.like(root.get("name"), "%" + searchText + "%"),
                    builder.like(root.get("code"), "%" + searchText + "%"),
                    builder.like(root.get("parentCode"), "%" + searchText + "%"),
                    builder.like(root.get("description"), "%" + searchText + "%"),
                    builder.like(root.get("guid"), "%" + searchText + "%"));
        };
    }

    public List<Contract> findAllByCompanyVendor(CompanyVendor vendor, LocalDate beginDate, LocalDate endDate) {
        Company company = vendor.getCompany();
        return contractRepository.findAll((root, query, cb) -> {
            Predicate p = cb.and(
                    cb.equal(root.get("company").get("id"), company.getId()),
                    cb.equal(root.get("payWay"), ContractPayWay.PAY));
            return applyPredicate(root, cb, p, beginDate, endDate);
        }, Sort.unsorted());
    }

    public List<Contract> findAllByCompanyCustomer(CompanyCustomer customer, LocalDate beginDate, LocalDate endDate) {
        Company company = customer.getCompany();
        return contractRepository.findAll((root, query, cb) -> {
            Predicate p = cb.and(
                    cb.equal(root.get("company"), company),
                    cb.equal(root.get("payWay"), ContractPayWay.RECEIVE));
            return applyPredicate(root, cb, p, beginDate, endDate);
        }, Sort.unsorted());
    }

    private Predicate applyPredicate(
            jakarta.persistence.criteria.Root<Contract> root,
            jakarta.persistence.criteria.CriteriaBuilder cb,
            Predicate base, LocalDate beginDate, LocalDate endDate) {
        if (beginDate == null && endDate == null) {
            return base;
        } else if (endDate == null) {
            return cb.and(base, cb.greaterThanOrEqualTo(root.get("setupDate"), beginDate));
        } else if (beginDate == null) {
            return cb.and(base, cb.lessThanOrEqualTo(root.get("setupDate"), endDate));
        } else {
            return cb.and(base, cb.between(root.get("setupDate"), beginDate, endDate));
        }
    }

    /**
     * 判断是否是非子合同编号
     */
    public boolean isSubContractCode(String code) {
        if (!StringUtils.hasText(code)) {
            return false;
        }
        return code.contains("-");
    }

    public boolean isSubContract(Contract contract) {
        return isSubContractCode(contract.getCode());
    }

    public String getParentCode(Contract contract) {
        String code = contract.getCode();

        int index = code.indexOf("-");
        if (index != -1) {
            return code.substring(0, index);
        }
        return null;
    }

    /**
     * 更新合同的主合同编号(从当前合同的额合同编码上推测)
     */
    public boolean updateParentCode(Contract contract) {
        if (!isSubContract(contract)) {
            return false;
        }
        String parentCode = getParentCode(contract);
        // fixed
        if (Objects.equals(contract.getParentCode(), parentCode)) {
            // 已经相同,跳过
            return false;
        }
        Contract parentContract = null;
        List<Contract> list = contractRepository.findByCodeStartsWith(parentCode);
        if (list.isEmpty()) {
            throw new NoSuchElementException("没有找到以" + parentCode + "开头的合同");
        }
        for (Contract c : list) {
            if (!c.getCode().contains("-")) {
                parentContract = c;
                break;
            }
        }

        if (parentContract == null) {
            throw new NoSuchElementException("没有匹配到以" + parentCode + "开头的合同");
        }

        if (Objects.equals(contract.getParentCode(), parentContract.getCode())) {
            return false;
        }
        contract.setParentCode(parentContract.getCode());
        return true;
    }

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

    /**
     * 重置合同关联企业
     */
    public void resetTo(Company from, Company to) {
        List<Contract> list = contractRepository.findAllByCompanyId(from.getId());
        if (list.isEmpty()) {
            return;
        }
        for (Contract contract : list) {
            contract.setDescription(MyStringUtils.appendIfAbsent(contract.getDescription(), "转自 " + from.getId()));
            contract.setCompany(to);
        }
        contractRepository.saveAll(list);
    }

    public List<Contract> findAllByParent(Contract contract) {
        return contractRepository.findAllByParentCode(contract.getCode());
    }

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

    public List<Contract> findByCodeStartsWith(ContractPayWay payWay, String parentCode) {
        return contractRepository.findByPayWayAndCodeStartsWith(payWay, parentCode);
    }
}
