package com.xisc.pm.infra.client;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xisc.pm.api.dto.CtPrcPredPlanTimeDTO;
import com.xisc.pm.domain.vo.EventFeignVO;
import com.xisc.pm.infra.feign.CtEventFeign;
import com.xisc.pm.infra.util.Utils;
import io.choerodon.core.exception.CommonException;
import io.choerodon.core.exception.ExceptionResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.hzero.core.util.ResponseUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * fileName: EventClient
 * description:
 * history:
 *
 * @author 张建元 jianyuan.zhang@hand-china.com
 * @version 1.0.0
 * @since 2024-07-26 09:17
 */
@Slf4j
@Component
public class EventClient {
    private static final ThreadLocal<List<EventFeignVO>> EVENT_REQUEST = new InheritableThreadLocal<>();
    private final CtEventFeign ctEventFeign;
    @Autowired
    private QeApiClient qeApiClient;
    
    @Value("${xisc.pm.ctevent.debug.flag:false}")
    private Boolean debugFlag ;

    @Autowired
    public EventClient(CtEventFeign ctEventFeign) {
        this.ctEventFeign = ctEventFeign;
    }

    /**
     * 发送事件。
     * <p>
     * 通过Feign客户端向特定租户发送事件。事件数据通过JSON序列化为字符串，然后设置为事件上下文。
     * 如果序列化过程中发生异常，则抛出通用异常。
     *
     * @param tenantId  租户ID，用于指定事件的接收租户。
     * @param eventCode 事件代码，用于标识事件的类型。
     * @param t         事件的具体数据，可以是任何类型。这个数据会被序列化为JSON字符串，作为事件的上下文发送。
     * @param clazz     返回数据类型
     * @param <T>       事件内容泛型
     * @param <D>       返回数据泛型
     * @return 解析数据
     * @throws CommonException 如果JSON序列化过程中发生异常或返回失败信息，则抛出此异常。
     */
    public <T, D> D sendEvent(Long tenantId, String eventCode, T t, Class<D> clazz) {
        // 事件请求
        ResponseEntity<String> result = getStringResponseEntity(tenantId, eventCode, t);
        if (ResponseUtils.isFailed(result)) {
            if(!debugFlag) {
                // 获取异常信息
                ExceptionResponse response = Utils.getExceptionResponse(result);
                throw new CommonException("pm.call_inface_info", "合同", response.getMessage());
            } else {
                return null;
            }
        } else {
            return ResponseUtils.getResponse(result, clazz);
        }
    }

    /**
     * 发送事件无返回结果，如果返回失败会抛出异常。
     * <p>
     * 通过Feign客户端向特定租户发送事件。事件数据通过JSON序列化为字符串，然后设置为事件上下文。
     * 如果序列化过程中发生异常，则抛出通用异常。
     *
     * @param tenantId  租户ID，用于指定事件的接收租户。
     * @param eventCode 事件代码，用于标识事件的类型。
     * @param t         事件的具体数据，可以是任何类型。这个数据会被序列化为JSON字符串，作为事件的上下文发送。
     * @throws CommonException 如果JSON序列化过程中发生异常或返回失败信息，则抛出此异常。
     */
    public <T> void sendEventWithOutResponse(Long tenantId, String eventCode, T t) {
        // 事件请求
        ResponseEntity<String> result = getStringResponseEntity(tenantId, eventCode, t);
        if (ResponseUtils.isFailed(result)) {
            if(!debugFlag) {
                // 获取异常信息
                ExceptionResponse response = Utils.getExceptionResponse(result);
                throw new CommonException("pm.call_inface_info", "合同", response.getMessage());
            }
        }
    }

    /**
     * 根据提供的租户ID和事件代码，将任意事件数据封装为JSON字符串后发送
     *
     * @param tenantId  租户ID，用于指定事件的目标租户
     * @param eventCode 事件代码，用于标识事件的类型
     * @param t         任意类型的事件数据，该数据将被序列化为JSON字符串后发送
     * @return 返回包含事件响应的ResponseEntity对象
     */
    private <T> ResponseEntity<String> getStringResponseEntity(Long tenantId, String eventCode, T t) {
        EventFeignVO eventRequest = getEventFeignVO(eventCode, t);
        // 调用 Feign 客户端，向指定租户发送事件
        ResponseEntity<String> responseEntity = ctEventFeign.eventExecute(tenantId, eventRequest);
        log.info("调用CT-EVENT-API，返回：" + responseEntity.getBody());
        return responseEntity;
    }

    private static <T> EventFeignVO getEventFeignVO(String eventCode, T t) {
        // 创建事件请求对象，用于封装事件相关信息
        EventFeignVO eventRequest = new EventFeignVO();
        // 设置事件代码
        eventRequest.setEventCode(eventCode);

        // 创建 ObjectMapper 实例用于序列化事件数据
        ObjectMapper mapper = new ObjectMapper();
        try {
            // 将事件数据序列化为 JSON 字符串
            String json = mapper.writeValueAsString(t);
            // 将序列化的事件数据设置为事件上下文
            eventRequest.setEventContext(json);
            log.info(String.format("添加事件【%s】,参数【%s】", eventCode , json));
        } catch (JsonProcessingException e) {
            // 如果序列化过程中发生异常，则抛出通用异常
            throw new CommonException(e);
        }
        return eventRequest;
    }

    /**
     * 添加事件
     * 本方法通过给定的事件代码和事件数据，创建并添加一个新的事件请求到线程局部变量中
     * 主要用于事件驱动或事件处理机制中，为后续的事件分发和处理做准备
     *
     * @param eventCode 事件代码，用于标识事件的类型
     * @param t         事件数据，泛型参数允许任何类型的数据
     */
    public <T> void addEvent(String eventCode, T t) {
        //为空不传
        if (CollectionUtils.isEmpty((Collection<?>) t)) {
            return;
        }
        // 根据事件代码和数据对象创建事件请求对象
        EventFeignVO eventRequest = getEventFeignVO(eventCode, t);

        // 获取线程局部变量中存储的事件请求列表
        List<EventFeignVO> voList = EVENT_REQUEST.get();

        // 如果列表为空，则初始化一个新的列表
        if (voList == null) {
            voList = new ArrayList<>();
        }

        // 将创建的事件请求对象添加到列表中
        voList.add(eventRequest);

        // 将更新后的列表设置回线程局部变量
        EVENT_REQUEST.set(voList);
    }

    /**
     * 发送事件请求
     * 此方法将当前线程中累积的事件请求发送给事件处理服务
     * 它首先检查是否存在事件请求，如果存在，则一并发送
     * 如果发送失败，它将抛出一个异常，然后清除已发送的事件请求
     *
     * @param tenantId 租户ID，用于区分不同租户的事件请求
     * @throws CommonException 如果事件请求发送失败，则抛出此异常
     */
    public void sendEvent(Long tenantId) {
        try {
            // 获取当前线程的事件请求列表
            List<EventFeignVO> voList = EVENT_REQUEST.get();
            // 如果列表为空，则直接返回，不执行后续操作
            if (CollectionUtils.isEmpty(voList)) {
                return;
            }
            // 通过Feign客户端调用事件处理服务，执行批量事件请求
            ResponseEntity<String> result = ctEventFeign.eventExecute(tenantId, voList);
            log.info("调用CT-EVENT-API，返回：" + result.getBody());
            // 如果请求失败，则获取错误信息并抛出异常
            if (ResponseUtils.isFailed(result)) {
                if(!debugFlag) {
                    // 解析响应，获取异常详细信息
                    ExceptionResponse response = Utils.getExceptionResponse(result);
                    // 根据异常信息抛出运行时异常
                    throw new CommonException("pm.call_inface_info", "合同", response.getMessage());
                }
            }
        } finally {
            // 清除已发送的事件请求，无论成功还是失败
            remove();
        }
    }
    
    public void remove() {
        EVENT_REQUEST.remove();
    }

    public List<CtPrcPredPlanTimeDTO> planDeliveryCompletedApi(Long tenantId, List<String> orderNoList) {
        // 调用 Feign 客户端，向指定租户发送事件
        log.info("调用CT-API【planDeliveryCompletedApi】，入参：tenantId = {}, orderNoList = {}", tenantId, orderNoList);
        ResponseEntity<String> result = ctEventFeign.planDeliveryCompletedApi(tenantId, orderNoList);
        log.info("调用CT-API【planDeliveryCompletedApi】，返回：" + result.getBody());
        if (ResponseUtils.isFailed(result)) {
            // 获取异常信息
            ExceptionResponse response = Utils.getExceptionResponse(result);
            throw new CommonException("pm.call_inface_info", "合同",response.getMessage());
        }
        return qeApiClient.dealResponse(result, CtPrcPredPlanTimeDTO.class);
    }
}
