package com.ecep.contract.cloud.rk;

import java.io.File;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

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.BlackReasonType;
import com.ecep.contract.IEntityService;
import com.ecep.contract.cloud.CloudInfo;
import com.ecep.contract.ds.company.repository.CompanyBlackReasonRepository;
import com.ecep.contract.ds.company.repository.CompanyOldNameRepository;
import com.ecep.contract.ds.company.service.CompanyService;
import com.ecep.contract.ds.other.service.SysConfService;
import com.ecep.contract.model.CloudRk;
import com.ecep.contract.model.Company;
import com.ecep.contract.model.CompanyBlackReason;
import com.ecep.contract.util.FileUtils;
import com.ecep.contract.util.HttpJsonUtils;
import com.ecep.contract.util.MyStringUtils;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import jakarta.persistence.criteria.Path;
import lombok.Data;

@Lazy
@Service
@CacheConfig(cacheNames = "cloud-rk")
public class CloudRkService implements IEntityService<CloudRk> {
    private static final Logger logger = LoggerFactory.getLogger(CloudRkService.class);

    public final static String ENTERPRISE_CREDIT_REPORT = "企业征信报告";

    public static final String KEY_PROXY = "cloud.rk.proxy";
    public static final String KEY_SYNC_ELAPSE = "cloud.rk.sync.elapse";
    public static final long DEFAULT_SYNC_ELAPSE = 36000L;
    public static final String KEY_VENDOR_REPORT_URL = "cloud.rk.vendor.report.url";
    public static final String KEY_CUSTOMER_REPORT_URL = "cloud.rk.customer.report.url";
    public static final String KEY_ENT_SCORE_URL = "cloud.rk.ent_score.url";
    public static final String KEY_ENT_REPORT_URL = "cloud.rk.ent_report.url";
    public static final String KEY_ENT_FUZZY_URL = "cloud.rk.ent_fuzzy.url";
    public static final String KEY_BLACK_LIST_URL = "cloud.rk.black_list.url";
    public static final String KEY_BLACK_LIST_ELAPSE = "cloud.rk.black_list.elapse";

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 同步超时， 单位 毫秒
     *
     * @return 毫秒
     */
    public long getSyncElapse() {
        String string = confService.getString(KEY_SYNC_ELAPSE);
        if (!StringUtils.hasText(string)) {
            return DEFAULT_SYNC_ELAPSE;
        }
        return MyStringUtils.toLong(string, DEFAULT_SYNC_ELAPSE);
    }

    @Data
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class EntInfo {
        @JsonAlias("entid")
        private String id;
        @JsonAlias("entname")
        private String name;
        private boolean nowName;
    }

    @Autowired
    private SysConfService confService;
    @Autowired
    private CloudRkRepository cloudRKRepository;
    @Lazy
    @Autowired
    private CompanyService companyService;
    @Autowired
    private CompanyOldNameRepository companyOldNameRepository;
    @Autowired
    private CompanyBlackReasonRepository companyBlackReasonRepository;

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

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

    @Override
    public Specification<CloudRk> getSpecification(String searchText) {
        if (!StringUtils.hasText(searchText)) {
            return null;
        }
        return (root, query, builder) -> {
            Path<Object> company = root.get("company");
            return builder.or(
                    builder.like(company.get("name"), "%" + searchText + "%"),
                    builder.like(company.get("shortName"), "%" + searchText + "%"),
                    builder.like(root.get("cloudId"), "%" + searchText + "%"),
                    builder.like(root.get("description"), "%" + searchText + "%"));
        };
    }

    /**
     * 更新黑名单列表
     */
    public void updateBlackList(
            Company company, CloudRk cloudRk, BlackListUpdateContext context) throws IOException {
        List<String> companyNames = new ArrayList<>();
        companyNames.add(company.getName());
        // fixed 平台API使用企业名称，可能记录的是曾用名
        companyOldNameRepository.findAllByCompanyId(company.getId()).forEach(oldName -> {
            // 歧义的曾用名不采用
            if (oldName.getAmbiguity()) {
                return;
            }
            companyNames.add(oldName.getName());
        });

        List<CompanyBlackReason> reasonList = new ArrayList<>();
        List<CompanyBlackReason> dbReasons = companyBlackReasonRepository.findAllByCompany(company);

        companyNames.forEach(name -> {
            String url = context.getUrl() + URLEncoder.encode(name, StandardCharsets.UTF_8);
            try {
                HttpJsonUtils.get(url, json -> {
                    if (!json.has("success") || !json.get("success").asBoolean()) {
                        System.out.println("json = " + json.toPrettyString());
                        return;
                    }

                    if (json.has("data")) {
                        JsonNode data = json.get("data");
                        try {
                            if (data.has("blackReason")) {
                                for (JsonNode reason : data.get("blackReason")) {
                                    toCompanyBlackReasonList(company, BlackReasonType.BLACK, reason, dbReasons,
                                            reasonList, context.getObjectMapper());
                                }
                            }
                            if (data.has("greyReason")) {
                                for (JsonNode reason : data.get("greyReason")) {
                                    toCompanyBlackReasonList(company, BlackReasonType.GRAY, reason, dbReasons,
                                            reasonList, context.getObjectMapper());
                                }
                            }
                        } catch (Exception ex) {
                            logger.error("{} {},json = {}", company.getName(), ex.getMessage(), json, ex);
                            throw new RuntimeException(json.toString(), ex);
                        }
                    }

                    // 保存JSON数据到公司目录
                    String companyPath = company.getPath();
                    if (StringUtils.hasText(companyPath)) {
                        File dir = new File(companyPath);
                        if (dir.exists()) {
                            File file = new File(dir, FileUtils.FILE_BLACK_LIST_JSON);
                            try {
                                objectMapper.writeValue(file, json);
                            } catch (IOException e) {
                                logger.warn("Unable Save BlackList to {}, company:{}", file, company.getName(), e);
                            }
                        }
                    }
                }, context.getObjectMapper(), context.getSocksProxy());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

        if (!reasonList.isEmpty()) {
            companyBlackReasonRepository.saveAll(reasonList);
        }
        cloudRk.setCloudBlackListUpdated(LocalDateTime.now());
    }

    private void toCompanyBlackReasonList(
            Company company, BlackReasonType type,
            JsonNode reason, List<CompanyBlackReason> dbReasons,
            List<CompanyBlackReason> reasonList, ObjectMapper objectMapper) throws JsonMappingException {
        ObjectNode object = (ObjectNode) reason;
        String key = "rk-" + object.remove("id").asText();
        CompanyBlackReason cbr = dbReasons.stream().filter(r -> r.getKey().equals(key)).findAny()
                .orElseGet(CompanyBlackReason::new);
        objectMapper.updateValue(cbr, reason);
        cbr.setCompany(company);
        cbr.setType(type);
        cbr.setKey(key);
        reasonList.add(cbr);
    }

    public CompletableFuture<BlackListUpdateContext> createBlackListUpdateContext() {
        return CompletableFuture.supplyAsync(() -> {
            BlackListUpdateContext context = new BlackListUpdateContext();
            context.setObjectMapper(objectMapper);
            context.setProxy(confService.getString(KEY_PROXY));
            context.setUrl(confService.getString(KEY_BLACK_LIST_URL));
            // context.setElapse(confService.getLong(KEY_BLACK_LIST_ELAPSE));
            return context;
        });
    }

    /**
     * @return true
     */
    public boolean checkBlackListUpdateElapse(
            Company company, CloudRk cloudRk, BlackListUpdateContext context) {
        LocalDateTime start = cloudRk.getCloudBlackListUpdated();
        if (start == null) {
            return true;
        }
        LocalDateTime elapse = start.plusSeconds(context.getElapse());
        return elapse.isBefore(LocalDateTime.now());
    }

    public CloudRk getOrCreateCloudRk(CloudInfo info) {
        Optional<CloudRk> optional = cloudRKRepository.findById(info.getId());
        return optional.orElseGet(() -> getOrCreateCloudRk(info.getCompany()));
    }

    /**
     * 返回或者创建 Cloud RK
     *
     * @param company 公司对象
     * @return Cloud RK
     */
    public CloudRk getOrCreateCloudRk(Company company) {
        Integer companyId = company.getId();
        List<CloudRk> list = cloudRKRepository.findAllByCompanyId(companyId);
        if (list.isEmpty()) {
            CloudRk rk = new CloudRk();
            rk.setCompany(company);
            rk.setCustomerGrade("");
            rk.setCustomerScore(-1);
            rk.setVendorGrade("");
            rk.setVendorScore(-1);
            rk.setRank("");
            return cloudRKRepository.save(rk);
        }
        if (list.size() == 1) {
            return list.getFirst();
        }

        // 查询有 CloudId 的记录
        List<CloudRk> hasCouldIdList = list.stream().filter(v -> StringUtils.hasText(v.getCloudId()))
                .collect(Collectors.toList());
        // 没有匹配到一条时
        if (hasCouldIdList.isEmpty()) {
            // 保留第一条，其他删除
            CloudRk rk = list.removeFirst();
            cloudRKRepository.deleteAll(list);
            return rk;
        }

        // 只有匹配到一条有 CloudId 的记录
        if (hasCouldIdList.size() == 1) {
            // 保留匹配的记录，其他删除
            CloudRk rk = hasCouldIdList.removeFirst();
            list.remove(rk);
            cloudRKRepository.deleteAll(list);
            return rk;
        }

        // 查询有 CloudLatest 的记录
        List<CloudRk> hasLatestList = hasCouldIdList.stream().filter(v -> v.getCloudLatest() != null)
                .collect(Collectors.toList());
        // 没有匹配到一条时
        if (hasLatestList.isEmpty()) {
            // 保留第一条，其他删除
            CloudRk rk = hasCouldIdList.removeFirst();
            list.remove(rk);
            cloudRKRepository.deleteAll(list);
            return rk;
        }

        // 只有匹配到一条有 CloudId 的记录
        if (hasLatestList.size() == 1) {
            // 保留匹配的记录，其他删除
            CloudRk rk = hasLatestList.removeFirst();
            list.remove(rk);
            cloudRKRepository.deleteAll(list);
            return rk;
        }

        return hasLatestList.getFirst();
    }

    /**
     * 保存 Cloud Rk
     *
     * @param cloudRk Cloud Rk 对象
     * @return 更新的 Cloud Rk
     */
    @CacheEvict(key = "#p0.id")
    public CloudRk save(CloudRk cloudRk) {
        return cloudRKRepository.save(cloudRk);
    }

    @CacheEvict(key = "#p0.id")
    @Override
    public void delete(CloudRk entity) {
        cloudRKRepository.delete(entity);
    }

    /**
     * 返回 在 {@link #getSyncElapse()} 毫秒前，更新的
     *
     * @return 记录条数
     */
    public long countNeedUpdate() {
        Instant now = Instant.now();
        long elapse = getSyncElapse();
        Instant instant = now.minusSeconds(elapse);
        return cloudRKRepository.countByAutoUpdateIsTrueAndLatestUpdateBefore(instant);
    }

    /**
     * 返回距离上次更新超过 SysConfig：cloud.rk.black_list.elapse 秒的公司
     */
    // @Transactional
    public List<CloudRk> findNeedUpdate() {
        Instant now = Instant.now();
        long elapse = getSyncElapse();
        Instant instant = now.minusSeconds(elapse);
        return cloudRKRepository.findTop100ByAutoUpdateIsTrueAndLatestUpdateBeforeOrderByLatestUpdateDesc(instant);
    }

    @CacheEvict
    public void deleteByCompany(Company company) {
        int deleted = cloudRKRepository.deleteAllByCompany(company);
        if (deleted > 0) {
            if (logger.isInfoEnabled()) {
                logger.info("Delete {} records by company:#{}", deleted, company.getId());
            }
        }
    }

    // TODO 这个可以无法更新缓存
    @Caching(evict = {
            @CacheEvict(key = "#p0.id"),
            @CacheEvict(key = "#p1.id"),
    })
    public void resetTo(Company from, Company to) {
        List<CloudRk> list = cloudRKRepository.findAllByCompanyId(from.getId());
        for (CloudRk item : list) {
            item.setCompany(to);
        }
        cloudRKRepository.saveAll(list);
    }
}
