package cn.stylefeng.team.project.modular.filing.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.stylefeng.roses.kernel.file.api.pojo.response.SysFileInfoResponse;
import cn.stylefeng.roses.kernel.file.modular.service.SysFileInfoService;
import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import cn.stylefeng.team.form.api.pojo.FormDesignVo;
import cn.stylefeng.team.meta.api.MetaFieldServiceApi;
import cn.stylefeng.team.meta.api.MetaModelServiceApi;
import cn.stylefeng.team.meta.api.entity.MetaModel;
import cn.stylefeng.team.meta.api.enums.SubTableTypeEnum;
import cn.stylefeng.team.meta.api.pojo.SubTableMetaFieldDTO;
import cn.stylefeng.team.project.api.entity.Business;
import cn.stylefeng.team.project.modular.business.service.BusinessService;
import cn.stylefeng.team.project.modular.filing.constants.AiConstants;
import cn.stylefeng.team.project.modular.filing.entity.BusinessAiParseLog;
import cn.stylefeng.team.project.modular.filing.factory.AiBusinessModelFactory;
import cn.stylefeng.team.project.modular.filing.factory.AutoViewConfigFactory;
import cn.stylefeng.team.project.modular.filing.pojo.aidesign.aiparse.ParseToBusinessRequest;
import cn.stylefeng.team.project.modular.filing.pojo.aidesign.aiparse.ParseToBusinessResult;
import cn.stylefeng.team.project.modular.filing.pojo.aidesign.aiparse.business.ParseModel;
import cn.stylefeng.team.project.modular.filing.pojo.aidesign.aiparse.dto.ParseModelPackageDTO;
import cn.stylefeng.team.project.modular.filing.pojo.flux.FluxResponse;
import cn.stylefeng.team.project.modular.filing.pojo.flux.enums.FluxResponseTypeEnum;
import cn.stylefeng.team.project.modular.filing.pojo.generateform.BaseMetaInfo;
import cn.stylefeng.team.project.modular.filing.service.*;
import cn.stylefeng.team.view.api.ViewApi;
import cn.stylefeng.team.view.api.pojo.ViewConfig;
import com.alibaba.fastjson2.JSON;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.request.ChatRequest;
import dev.langchain4j.model.chat.request.ChatRequestParameters;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxSink;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static cn.stylefeng.team.project.modular.filing.pojo.flux.enums.FluxResponseTypeEnum.BUSINESS_LOG_CREATE;

@Service
@Slf4j
public class AIDesignProcessServiceImpl implements AIDesignProcessService {

    @Resource
    private SysFileInfoService sysFileInfoService;

    @Resource
    private BusinessAiParseLogService businessAiParseLogService;

    @Resource
    private MetaModelServiceApi metaModelServiceApi;

    @Resource
    private BusinessService businessService;

    @Resource
    private BusinessExcelService businessExcelService;

    @Resource
    private BevProdFilingListService bevProdFilingListService;

    @Resource
    private FormAutoGenerateService formAutoGenerateService;

    @Resource
    private MetaFieldServiceApi metaFieldServiceApi;

    @Resource
    private ViewApi viewApi;

    @Override
    public Flux<FluxResponse> parseToBusinessFlux(ParseToBusinessRequest parseToBusinessRequest) {

        BusinessAiParseLog businessAiParseLog = businessAiParseLogService.add(parseToBusinessRequest.getBusinessId(), parseToBusinessRequest.getExcelFileId(), parseToBusinessRequest.getPngFileId());

        return Flux.create(sink -> {
            try {

                SysFileInfoResponse fileInfoResult = sysFileInfoService.getFileInfoResult(parseToBusinessRequest.getPngFileId());

                Long logId = businessAiParseLog.getId();
                FluxResponse.sinkNext(sink, FluxResponse.attachment(BUSINESS_LOG_CREATE, logId));

                CompletableFuture<String> modelInvokeFuture = this.parsePngToBusinessModel(fileInfoResult, sink);

                modelInvokeFuture.thenAccept(pngToBusinessJson -> {

                    ParseToBusinessResult parseToBusinessResult = JSON.parseObject(pngToBusinessJson, ParseToBusinessResult.class);

                    if (isParseLogFinish(sink, logId, businessAiParseLog, parseToBusinessResult, pngToBusinessJson)) {
                        return;
                    }

                    parseToBusinessResult.setParseToBusinessRequest(parseToBusinessRequest);
                    this.saveParseResultToModel(parseToBusinessResult, sink);

                    FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.SUCCESS_END, null));
                    sink.complete();
                }).exceptionally(throwable -> {
                    log.error("JSON转化业务遇到问题！", throwable);
                    this.processException("业务处理过程中遇到问题，请查阅日志！error：" + throwable.getMessage(), throwable, sink, businessAiParseLog);
                    return null;
                });
            }

            catch (ServiceException serviceException) {
                log.error("大模型解析图片遇到问题！", serviceException);
                this.processException(serviceException.getUserTip(), serviceException, sink, businessAiParseLog);
            }

            catch (Exception exception) {
                log.error("大模型解析过程遇到问题！", exception);
                this.processException("后台遇到一些问题，请重新开始业务生成！error：" + exception.getMessage(), exception, sink, businessAiParseLog);
            }
        });
    }

    private CompletableFuture<String> parsePngToBusinessModel(SysFileInfoResponse fileInfoResult, FluxSink<FluxResponse> sink) {

        FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.BEGIN_AI_INVOKE, null));

        CompletableFuture<String> resultFuture = new CompletableFuture<>();

        OpenAiStreamingChatModel openAiChatModel = OpenAiStreamingChatModel.builder()
                .baseUrl(AiConstants.SILICON_FLOW_BASE_URL)
                .apiKey(AiConstants.SILICON_API_KEY)
                .modelName(AiConstants.VL_MODEL_NAME)
                .strictJsonSchema(true)
                .logRequests(true)
                .logResponses(true)
                .timeout(Duration.ofMinutes(30))
                .build();

        ChatRequest chatRequest = ChatRequest.builder()
                .messages(SystemMessage.from(AiConstants.AI_BUSINESS_PARSE_PROMPT),
                        UserMessage.from(ImageContent.from(Base64.encode(fileInfoResult.getFileBytes()), "image/png", ImageContent.DetailLevel.HIGH)))
                .parameters(ChatRequestParameters.builder()
                        .temperature(0.1)
                        .topP(0.7)
                        .build())
                .build();

        openAiChatModel.chat(chatRequest, new StreamingChatResponseHandler() {
            private final StringBuilder responseBuilder = new StringBuilder();

            @Override
            public void onPartialResponse(String partialResponse) {
                responseBuilder.append(partialResponse);
                FluxResponse.sinkNext(sink, FluxResponse.writeJson(partialResponse));
            }

            @Override
            public void onCompleteResponse(ChatResponse completeResponse) {
                FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.ASSISTANT_RESPONSE_END, null));

                String resultString = responseBuilder.toString();
                if (resultString.contains("<|begin_of_box|>")) {
                    resultString = StrUtil.removeAll(resultString, "<|begin_of_box|>");
                }
                if (resultString.contains("<|end_of_box|>")) {
                    resultString = StrUtil.removeAll(resultString, "<|end_of_box|>");
                }

                resultFuture.complete(resultString);
            }

            @Override
            public void onError(Throwable error) {
                log.error("---------->>>>>>>>>>大模型报错：{}", error.getMessage());
                resultFuture.completeExceptionally(error);
            }
        });

        return resultFuture;
    }

    private boolean isParseLogFinish(FluxSink<FluxResponse> sink,
                                     Long logId,
                                     BusinessAiParseLog businessAiParseLog,
                                     ParseToBusinessResult parseToBusinessResult, String pngToBusinessJson) {
        boolean canContinue = this.businessAiParseLogService.validateAiParseCanContinue(logId);
        if (!canContinue) {
            FluxResponse.sinkNext(sink, FluxResponse.error("用户手动停止了解析过程，无需再继续解析"));
            sink.complete();
            return true;
        } else {

            this.businessAiParseLogService.updateSuccessLog(businessAiParseLog, pngToBusinessJson, parseToBusinessResult);
        }
        return false;
    }

    private void saveParseResultToModel(ParseToBusinessResult parseToBusinessResult, FluxSink<FluxResponse> sink) {

        ParseToBusinessRequest parseToBusinessRequest = parseToBusinessResult.getParseToBusinessRequest();

        this.metaModelServiceApi.cleanBusinessModel(parseToBusinessRequest.getBusinessId());
        FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.CLEAN_CURRENT_MODEL, null));

        List<ParseModel> businessModelList = parseToBusinessResult.getBusinessModelList();

        Business business = businessService.getById(parseToBusinessRequest.getBusinessId());
        ParseModelPackageDTO parseModelPackage = AiBusinessModelFactory.createParseModelPackage(business, businessModelList);

        this.metaModelServiceApi.savePackageModel(
                parseModelPackage.getMetaModelList(), parseModelPackage.getMetaDefinitionList(), parseModelPackage.getMetaFieldList());
        FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.CREATE_MODEL, parseModelPackage.getMetaModelList()));

        this.businessExcelService.cleanAndSaveExcel(parseToBusinessRequest.getBusinessId(), parseToBusinessRequest.getExcelFileId());

        bevProdFilingListService.updateBusinessFilingInfo(parseToBusinessResult);

        FormDesignVo formDesignVo = this.autoGenerateForm(parseToBusinessResult, parseToBusinessRequest, parseModelPackage);
        FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.CREATE_FORM, formDesignVo));

        this.addMainTableLinkVirtualField(parseModelPackage);

        Set<MetaModel> notMainTableList = parseModelPackage.getMetaModelList().stream()
                .filter(item -> !item.getMainFlag().equals(YesOrNotEnum.Y.getCode())).collect(Collectors.toSet());
        for (MetaModel metaModel : notMainTableList) {
            this.metaModelServiceApi.executeCreateTable(metaModel, metaModel.getMetaFieldList());
        }
        FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.EXECUTE_CREATE_TABLE, null));

        MetaModel mainModel = parseModelPackage.getMetaModelList().stream()
                .filter(item -> item.getMainFlag().equals(YesOrNotEnum.Y.getCode()))
                .findFirst().orElse(null);
        if (mainModel == null) {
            return;
        }
        ViewConfig formBasedViewConfig = AutoViewConfigFactory.createFormBasedViewConfig(mainModel.getMetaFieldList(), formDesignVo);
        this.viewApi.createListView(business.getBusinessId(), business.getBusinessCategoryId(), formBasedViewConfig);
        FluxResponse.sinkNext(sink, FluxResponse.attachment(FluxResponseTypeEnum.CREATE_VIEW, formBasedViewConfig));
    }

    private FormDesignVo autoGenerateForm(ParseToBusinessResult parseToBusinessResult, ParseToBusinessRequest parseToBusinessRequest, ParseModelPackageDTO parseModelPackage) {
        BaseMetaInfo baseMetaInfo = new BaseMetaInfo();
        baseMetaInfo.setBusinessId(parseToBusinessRequest.getBusinessId());
        baseMetaInfo.setMainTitle(parseToBusinessResult.getExcelMainTitle());
        baseMetaInfo.setSubTitle(parseToBusinessResult.getExcelSubTitle());
        baseMetaInfo.setFilingNo(parseToBusinessResult.getExcelNumber());
        baseMetaInfo.setFilingVersion(parseToBusinessResult.getFilingVersion());
        baseMetaInfo.setMetaModelList(parseModelPackage.getMetaModelList());
        baseMetaInfo.setMetaFieldList(parseModelPackage.getMetaFieldList());
        return formAutoGenerateService.autoGenerateForm(baseMetaInfo);
    }

    private void addMainTableLinkVirtualField(ParseModelPackageDTO parseModelPackage) {

        List<MetaModel> metaModelList = parseModelPackage.getMetaModelList();

        MetaModel mainModel = metaModelList.stream().filter(item -> item.getMainFlag().equals(YesOrNotEnum.Y.getCode())).findFirst().orElse(null);
        if (mainModel == null) {
            return;
        }

        List<MetaModel> subModelList = metaModelList.stream()
                .filter(item -> item.getMainFlag().equals(YesOrNotEnum.N.getCode())
                        && item.getSubTableType().equals(SubTableTypeEnum.ONE_TO_MANY.getCode()))
                .collect(Collectors.toList());

        List<SubTableMetaFieldDTO> subTableMetaFieldDTOList = new ArrayList<>();
        for (MetaModel subModel : subModelList) {
            SubTableMetaFieldDTO subTableMetaFieldDTO = new SubTableMetaFieldDTO(subModel.getMetaModelCode(), subModel.getMetaModelName());
            subTableMetaFieldDTOList.add(subTableMetaFieldDTO);
        }
        metaFieldServiceApi.addMainModelLinkSubTableVirtualField(mainModel.getMetaModelId(), subTableMetaFieldDTOList);
    }

    private void processException(String exceptionMessage, Throwable throwable,
                                  FluxSink<FluxResponse> sink, BusinessAiParseLog businessAiParseLog) {
        FluxResponse.sinkNext(sink, FluxResponse.error(exceptionMessage));
        sink.complete();

        this.businessAiParseLogService.updateErrorLog(throwable, businessAiParseLog);
    }

}
