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

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.stylefeng.roses.kernel.auth.api.context.LoginContext;
import cn.stylefeng.roses.kernel.db.api.factory.PageFactory;
import cn.stylefeng.roses.kernel.db.api.factory.PageResultFactory;
import cn.stylefeng.roses.kernel.db.api.pojo.page.PageResult;
import cn.stylefeng.roses.kernel.groovy.api.GroovyApi;
import cn.stylefeng.roses.kernel.rule.enums.YesOrNotEnum;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import cn.stylefeng.roses.kernel.rule.util.HttpServletUtil;
import cn.stylefeng.roses.kernel.rule.util.ResponseRenderUtil;
import cn.stylefeng.team.form.api.FormItemServiceApi;
import cn.stylefeng.team.form.api.FormModelServiceApi;
import cn.stylefeng.team.form.api.pojo.FormDesignVo;
import cn.stylefeng.team.generator.api.CodeGeneratorApi;
import cn.stylefeng.team.meta.api.MetaModelServiceApi;
import cn.stylefeng.team.meta.api.constants.MetaConstants;
import cn.stylefeng.team.meta.api.entity.MetaModel;
import cn.stylefeng.team.project.api.SqlProviderApi;
import cn.stylefeng.team.project.api.ViewDataAction;
import cn.stylefeng.team.project.api.enums.DataRenderTypeEnum;
import cn.stylefeng.team.project.api.pojo.BusinessRenderDetailVo;
import cn.stylefeng.team.project.api.pojo.BusinessSearchRequest;
import cn.stylefeng.team.project.api.pojo.SqlProviderResult;
import cn.stylefeng.team.project.api.pojo.request.BusinessDataRequest;
import cn.stylefeng.team.project.api.pojo.request.BusinessSaveRequest;
import cn.stylefeng.team.project.api.pojo.request.BusinessUpdateRequest;
import cn.stylefeng.team.project.modular.business.enums.BusinessExceptionEnum;
import cn.stylefeng.team.project.modular.business.pojo.request.BusinessDeleteBatchRequest;
import cn.stylefeng.team.project.modular.business.pojo.request.BusinessDeleteRequest;
import cn.stylefeng.team.project.modular.businessrender.factory.FormDataFactory;
import cn.stylefeng.team.project.modular.businessrender.factory.ModelDataFactory;
import cn.stylefeng.team.project.modular.businessrender.factory.ModelDataKeyFactory;
import cn.stylefeng.team.project.modular.businessrender.factory.ViewResultFactory;
import cn.stylefeng.team.project.modular.businessrender.pojo.MetaModelData;
import cn.stylefeng.team.project.modular.businessrender.pojo.request.BusinessRenderDetailRequest;
import cn.stylefeng.team.project.modular.businessrender.service.BusinessRenderService;
import cn.stylefeng.team.project.modular.businessrender.service.ModelDataService;
import cn.stylefeng.team.project.modular.businessrender.sql.ProjectSqlExecuteApi;
import cn.stylefeng.team.project.modular.filing.entity.BusinessFlow;
import cn.stylefeng.team.project.modular.filing.service.BusinessFlowService;
import cn.stylefeng.team.view.api.ViewApi;
import cn.stylefeng.team.view.api.pojo.ViewColumnField;
import cn.stylefeng.team.view.api.pojo.ViewConfig;
import cn.stylefeng.workflow.api.enums.WorkflowStartTypeEnum;
import cn.stylefeng.workflow.design.api.ModelApi;
import cn.stylefeng.workflow.instance.api.WorkflowControlApi;
import cn.stylefeng.workflow.instance.api.pojo.request.WorkflowStartRequest;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class BusinessRenderServiceImpl implements BusinessRenderService {

    @Resource
    private ViewApi viewApi;

    @Resource
    private ProjectSqlExecuteApi projectSqlExecuteApi;

    @Resource
    private FormItemServiceApi formItemServiceApi;

    @Resource
    private MetaModelServiceApi metaModelServiceApi;

    @Resource
    private CodeGeneratorApi codeGeneratorApi;

    @Resource
    private ModelDataService modelDataService;

    @Resource
    private SqlProviderApi sqlProviderApi;

    @Resource
    private FormModelServiceApi formModelServiceApi;

    @Resource
    private GroovyApi groovyApi;

    @Resource
    private BusinessFlowService businessFlowService;

    @Resource
    private ModelApi modelApi;

    @Resource
    private WorkflowControlApi workflowControlApi;

    @Override
    public PageResult<Map<String, Object>> getViewPageData(BusinessSearchRequest businessRenderRequest) {

        ViewConfig viewConfig = null;
        if (businessRenderRequest.getViewId() != null) {
            viewConfig = viewApi.getViewConfig(businessRenderRequest.getViewId(), false);
        } else {
            viewConfig = viewApi.getViewConfigByBusinessId(businessRenderRequest.getBusinessId(), false);
        }

        if (DataRenderTypeEnum.API.getCode().equals(viewConfig.getDataRenderType())) {
            return getCustomApiViewPageData(businessRenderRequest, viewConfig);
        }

        else if (DataRenderTypeEnum.JAVA_CODE.getCode().equals(viewConfig.getDataRenderType())) {
            return getJavaCodeViewPageData(businessRenderRequest, viewConfig);
        }

        else if (DataRenderTypeEnum.SQL.getCode().equals(viewConfig.getDataRenderType())) {
            return getSqlViewPageData(businessRenderRequest, viewConfig);
        }

        else if (DataRenderTypeEnum.AUTO.getCode().equals(viewConfig.getDataRenderType())) {
            return getFormBasedViewPageData(businessRenderRequest, viewConfig);
        }

        return null;
    }

    @Override
    public BusinessRenderDetailVo getDetail(BusinessRenderDetailRequest businessRenderDetailRequest) {

        String primaryKeyId = businessRenderDetailRequest.getPrimaryKeyId();

        Long businessId = businessRenderDetailRequest.getBusinessId();

        BusinessRenderDetailVo businessRenderDetailVo = new BusinessRenderDetailVo();
        businessRenderDetailVo.setPrimaryKeyId(primaryKeyId);

        List<FormDesignVo> businessFromDesignList = formItemServiceApi.getBusinessFromDesignList(businessId);

        Map<String, List<Map<String, Object>>> businessData = modelDataService.getBusinessDataByPrimaryKey(businessId, primaryKeyId);

        Map<String, Map<String, Object>> formData = FormDataFactory.createFormData(businessFromDesignList, businessData);
        businessRenderDetailVo.setFormDetail(formData);

        return businessRenderDetailVo;
    }

    @Override
    public void deleteBusiness(BusinessDeleteRequest businessDeleteRequest) {
        BusinessDeleteBatchRequest businessDeleteBatchRequest = new BusinessDeleteBatchRequest();
        businessDeleteBatchRequest.setBusinessId(businessDeleteRequest.getBusinessId());
        businessDeleteBatchRequest.setBatchDeleteIdList(ListUtil.toList(businessDeleteRequest.getPrimaryKeyId()));
        this.deleteBusinessBatch(businessDeleteBatchRequest);
    }

    @Override
    public void deleteBusinessBatch(BusinessDeleteBatchRequest businessDeleteBatchRequest) {

        Long businessId = businessDeleteBatchRequest.getBusinessId();

        List<Long> primaryKeyIdList = businessDeleteBatchRequest.getBatchDeleteIdList();

        List<String> primaryKeyIdListString = new ArrayList<>();
        for (Long primaryKeyId : primaryKeyIdList) {
            primaryKeyIdListString.add(primaryKeyId.toString());
        }

        List<MetaModel> businessMetaModelList = metaModelServiceApi.getBusinessMetaModelList(businessId, null);
        if (ObjectUtil.isEmpty(businessMetaModelList)) {
            throw new ServiceException(BusinessExceptionEnum.CANT_FIND_TABLES);
        }

        for (MetaModel model : businessMetaModelList) {

            if (YesOrNotEnum.Y.getCode().equals(model.getMainFlag())) {
                SqlProviderResult sqlProviderResult = sqlProviderApi.deleteByField(model.getMetaModelCode(), "id", primaryKeyIdListString);
                this.projectSqlExecuteApi.executeDelete(sqlProviderResult.getMybatisSqlTemplate(), sqlProviderResult.getSqlTemplateParams());
            }

            else {
                String fieldCode = model.getSubTableFieldCode();
                SqlProviderResult sqlProviderResult = sqlProviderApi.deleteByField(model.getMetaModelCode(), fieldCode, primaryKeyIdListString);
                this.projectSqlExecuteApi.executeDelete(sqlProviderResult.getMybatisSqlTemplate(), sqlProviderResult.getSqlTemplateParams());
            }
        }
    }

    @Override
    public void exportData(BusinessSearchRequest businessSearchRequest) {

        ViewConfig viewConfig = null;
        if (businessSearchRequest.getViewId() != null) {
            viewConfig = viewApi.getViewConfig(businessSearchRequest.getViewId(), true);
        } else {
            viewConfig = viewApi.getViewConfigByBusinessId(businessSearchRequest.getBusinessId(), true);
        }

        List<Map<String, Object>> totalExportData = null;
        if (DataRenderTypeEnum.API.getCode().equals(viewConfig.getDataRenderType())) {
            totalExportData = getCustomApiViewExportData(businessSearchRequest, viewConfig);
        } else if (DataRenderTypeEnum.JAVA_CODE.getCode().equals(viewConfig.getDataRenderType())) {
            totalExportData = getJavaCodeViewTotalData(businessSearchRequest, viewConfig);
        } else if (DataRenderTypeEnum.AUTO.getCode().equals(viewConfig.getDataRenderType())) {
            totalExportData = getFormBasedViewTotalData(businessSearchRequest, viewConfig);
        }

        else {
            String viewListSql = this.getViewListSql(businessSearchRequest, viewConfig);
            totalExportData = this.projectSqlExecuteApi.findList(viewListSql, new HashMap<>());// todo
        }

        createExcelFileAndDownload(totalExportData, viewConfig);
    }

    @Override
    public void createExcelFileAndDownload(List<Map<String, Object>> totalExportData, ViewConfig viewConfig) {
        List<Map<String, Object>> beautifulViewRecord = ViewResultFactory.parseViewListResult(totalExportData, viewConfig, viewApi);



        List<List<String>> dynamicTableHead = getDynamicTableHead(viewConfig);
        List<List<Object>> finalResultData = ViewResultFactory.parseToExcelLine(viewConfig, dynamicTableHead, beautifulViewRecord);



        HttpServletResponse response = HttpServletUtil.getResponse();
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        ResponseRenderUtil.setRenderFileHeader(response, "export_data.xlsx");
        EasyExcel.write(outputStream)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .head(dynamicTableHead).sheet("数据")
                .doWrite(finalResultData);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String save(BusinessSaveRequest businessSaveRequest) {

        List<MetaModel> businessMetaModelList = metaModelServiceApi.getBusinessMetaModelList(businessSaveRequest.getBusinessId(), null);

        List<Long> formDefinitionIds = businessSaveRequest.getFormDetail().keySet().stream().map(Long::valueOf)
                .collect(Collectors.toList());
        List<FormDesignVo> businessFormDesignList = formModelServiceApi.getFromDesignList(formDefinitionIds);

        MetaModelData modelData = ModelDataFactory.createModelDataByFormData(IdWorker.getId(), businessMetaModelList,
                businessFormDesignList, businessSaveRequest.getFormDetail());

        ModelDataKeyFactory.processCommonDateBeforeSave(modelData, businessMetaModelList);

        SqlProviderResult insert = this.sqlProviderApi.insert(modelData.getMainTableName(), modelData.getMainTableData());
        this.projectSqlExecuteApi.executeUpdate(insert.getMybatisSqlTemplate(), insert.getSqlTemplateParams());

        for (Map.Entry<String, List<Map<String, Object>>> subTableEntry : modelData.getSubTableData().entrySet()) {
            String tableName = subTableEntry.getKey();
            List<Map<String, Object>> tableRecords = subTableEntry.getValue();
            for (Map<String, Object> tableRecord : tableRecords) {
                SqlProviderResult tempResult = this.sqlProviderApi.insert(tableName, tableRecord);
                this.projectSqlExecuteApi.executeUpdate(tempResult.getMybatisSqlTemplate(), tempResult.getSqlTemplateParams());
            }
        }

        Object primaryKey = modelData.getMainTableData().get(MetaConstants.COMMON_ID_FILED_CODE);

        this.doFlowStart(businessSaveRequest, primaryKey);

        if (primaryKey != null) {
            return primaryKey.toString();
        } else {
            return null;
        }
    }

    @Override
    public BusinessRenderDetailVo getBusinessRenderDetail(BusinessDataRequest businessDataRequest) {

        String primaryKeyId = businessDataRequest.getPrimaryKeyId();

        List<Long> formDefinitionIdList = businessDataRequest.getFormDefinitionIdList();

        Long formDefinitionId = formDefinitionIdList.iterator().next();
        Long businessId = formModelServiceApi.getFormBusinessId(Convert.toLong(formDefinitionId));
        if (businessId == null) {
            return null;
        }

        List<FormDesignVo> businessFromDesignList = formModelServiceApi.getFromDesignList(formDefinitionIdList);

        Map<String, List<Map<String, Object>>> businessData = modelDataService.getBusinessDataByPrimaryKey(businessId, primaryKeyId);

        Map<String, Map<String, Object>> formData = FormDataFactory.createFormData(businessFromDesignList, businessData);

        BusinessRenderDetailVo businessRenderDetailVo = new BusinessRenderDetailVo();
        businessRenderDetailVo.setPrimaryKeyId(primaryKeyId);
        businessRenderDetailVo.setFormDetail(formData);
        return businessRenderDetailVo;
    }

    @Override
    public void update(BusinessUpdateRequest businessUpdateRequest) {

        List<MetaModel> businessMetaModelList = metaModelServiceApi.getBusinessMetaModelList(businessUpdateRequest.getBusinessId(), null);

        List<Long> formDefinitionIds = businessUpdateRequest.getFormDetail().keySet().stream().map(Long::valueOf)
                .collect(Collectors.toList());
        List<FormDesignVo> businessFormDesignList = formModelServiceApi.getFromDesignList(formDefinitionIds);

        MetaModelData modelData = ModelDataFactory.createModelDataByFormData(businessUpdateRequest.getPrimaryKeyId(), businessMetaModelList,
                businessFormDesignList,
                businessUpdateRequest.getFormDetail());

        ModelDataKeyFactory.processCommonDataBeforeUpdate(modelData, businessMetaModelList);

        SqlProviderResult update = this.sqlProviderApi.update(modelData.getMainTableName(), modelData.getPrimaryKeyId(),
                modelData.getMainTableData());
        this.projectSqlExecuteApi.executeUpdate(update.getMybatisSqlTemplate(), update.getSqlTemplateParams());

        Map<String, List<Map<String, Object>>> subTableData = modelData.getSubTableData();
        for (Map.Entry<String, List<Map<String, Object>>> subTableEntry : subTableData.entrySet()) {

            String tableName = subTableEntry.getKey();

            List<Map<String, Object>> tableRecords = subTableEntry.getValue();

            String fieldCode = null;
            for (MetaModel metaModel : businessMetaModelList) {
                if (metaModel.getMetaModelCode().equals(tableName)) {
                    fieldCode = metaModel.getSubTableFieldCode();
                    break;
                }
            }

            SqlProviderResult sqlProviderResult = sqlProviderApi.deleteByField(tableName, fieldCode,
                    ListUtil.toList(String.valueOf(businessUpdateRequest.getPrimaryKeyId())));
            this.projectSqlExecuteApi.executeDelete(sqlProviderResult.getMybatisSqlTemplate(), sqlProviderResult.getSqlTemplateParams());

            for (Map<String, Object> tableRecord : tableRecords) {
                SqlProviderResult insert = this.sqlProviderApi.insert(tableName, tableRecord);
                this.projectSqlExecuteApi.executeUpdate(insert.getMybatisSqlTemplate(), insert.getSqlTemplateParams());
            }
        }
    }

    @Override
    public PageResult<Map<String, Object>> commonGetViewPageResult(BusinessSearchRequest businessRenderRequest, ViewConfig viewConfig, String finalSql) {
        Page<Map<String, Object>> objectPage = PageFactory.defaultPage(businessRenderRequest);
        List<Map<String, Object>> records = this.projectSqlExecuteApi.customFindPage(objectPage, finalSql, businessRenderRequest.getBusinessSearchParams());

        List<Map<String, Object>> beautifulViewRecord = ViewResultFactory.parseViewListResult(records, viewConfig, viewApi);

        objectPage.setRecords(beautifulViewRecord);
        return PageResultFactory.createPageResult(objectPage);
    }

    private String getViewListSql(BusinessSearchRequest businessSearchRequest, ViewConfig viewConfig) {

        String sqlTemplate = viewConfig.getSqlTemplate();
        if (StrUtil.isBlank(sqlTemplate)) {
            throw new ServiceException(BusinessExceptionEnum.SQL_NOT_CONFIG);
        }

        Map<String, Object> businessSearchParams = businessSearchRequest.getBusinessSearchParams();
        if (ObjectUtil.isEmpty(businessSearchParams)) {
            businessSearchParams = new HashMap<>();
        }

        return codeGeneratorApi.codeGenerate(sqlTemplate, businessSearchParams);
    }

    private List<List<String>> getDynamicTableHead(ViewConfig viewConfig) {

        List<List<String>> list = new ArrayList<>();

        List<ViewColumnField> viewColumnFieldList = viewConfig.getViewColumnFieldList();
        for (ViewColumnField viewColumnField : viewColumnFieldList) {
            List<String> headItem = new ArrayList<String>();
            headItem.add(viewColumnField.getShowName());
            list.add(headItem);
        }

        return list;
    }

    private PageResult<Map<String, Object>> getCustomApiViewPageData(BusinessSearchRequest businessRenderRequest,
                                                                     ViewConfig viewConfig) {
        ViewDataAction viewDataAction = getViewDataAction(viewConfig);
        return viewDataAction.getCustomViewData(businessRenderRequest);
    }

    private List<Map<String, Object>> getCustomApiViewExportData(BusinessSearchRequest businessRenderRequest,
                                                                 ViewConfig viewConfig) {
        ViewDataAction viewDataAction = getViewDataAction(viewConfig);
        return viewDataAction.getCustomViewTotalData(businessRenderRequest);
    }

    private ViewDataAction getViewDataAction(ViewConfig viewConfig) {

        String springBeanClass = viewConfig.getSpringBeanClass();
        Class<?> clazz = null;
        try {
            clazz = Class.forName(springBeanClass);
        } catch (ClassNotFoundException e) {
            throw new ServiceException(BusinessExceptionEnum.VIEW_API_CLASS_NOT_FOUND);
        }

        ViewDataAction viewDataAction = null;
        try {
            viewDataAction = (ViewDataAction) SpringUtil.getBean(clazz);
        } catch (Exception e) {
            throw new ServiceException(BusinessExceptionEnum.VIEW_API_CLASS_NOT_FOUND);
        }
        return viewDataAction;
    }

    @SuppressWarnings("unchecked")
    private PageResult<Map<String, Object>> getJavaCodeViewPageData(BusinessSearchRequest businessRenderRequest, ViewConfig viewConfig) {
        return (PageResult<Map<String, Object>>) groovyApi.executeClassMethod(
                viewConfig.getJavaCode(),
                "getCustomViewData",
                new Class[]{BusinessSearchRequest.class},
                new Object[]{businessRenderRequest});
    }

    @SuppressWarnings("unchecked")
    private List<Map<String, Object>> getJavaCodeViewTotalData(BusinessSearchRequest businessRenderRequest, ViewConfig viewConfig) {
        return (List<Map<String, Object>>) groovyApi.executeClassMethod(
                viewConfig.getJavaCode(),
                "getCustomViewTotalData",
                new Class[]{BusinessSearchRequest.class},
                new Object[]{businessRenderRequest});
    }

    private PageResult<Map<String, Object>> getSqlViewPageData(BusinessSearchRequest businessRenderRequest, ViewConfig viewConfig) {

        String viewListSql = viewConfig.getSqlTemplate();

        return commonGetViewPageResult(businessRenderRequest, viewConfig, viewListSql);
    }

    private PageResult<Map<String, Object>> getFormBasedViewPageData(BusinessSearchRequest businessRenderRequest, ViewConfig viewConfig) {

        String finalSql = calcFormBasedViewSelectSql(businessRenderRequest, viewConfig);

        return commonGetViewPageResult(businessRenderRequest, viewConfig, finalSql);
    }

    private List<Map<String, Object>> getFormBasedViewTotalData(BusinessSearchRequest businessSearchRequest, ViewConfig viewConfig) {

        String finalSql = calcFormBasedViewSelectSql(businessSearchRequest, viewConfig);

        List<Map<String, Object>> records = this.projectSqlExecuteApi.findList(finalSql, new HashMap<>());

        return ViewResultFactory.parseViewListResult(records, viewConfig, viewApi);
    }

    private String calcFormBasedViewSelectSql(BusinessSearchRequest businessRenderRequest, ViewConfig viewConfig) {

        MetaModel mainMetaModel = metaModelServiceApi.getMainMetaModel(businessRenderRequest.getBusinessId());

        List<ViewColumnField> viewColumnFieldList = viewConfig.getViewColumnFieldList();
        Set<String> collect = viewColumnFieldList.stream().map(ViewColumnField::getFieldName).collect(Collectors.toSet());
        return this.sqlProviderApi.selectSql(new ArrayList<>(collect), mainMetaModel.getMetaModelCode(), businessRenderRequest.getBusinessSearchParams());
    }

    private void doFlowStart(BusinessSaveRequest businessSaveRequest, Object primaryKey) {

        BusinessFlow businessFlow = businessFlowService.queryBusinessLinkFlow(businessSaveRequest.getBusinessId());
        if (businessFlow == null || businessFlow.getFlowModelId() == null) {
            return;
        }

        Long linkFlowModelId = businessFlow.getFlowModelId();
        Long flowDefinitionId = modelApi.getModelLatestDefinitionId(linkFlowModelId);

        WorkflowStartRequest workflowStartRequest = new WorkflowStartRequest();

        workflowStartRequest.setPrimaryKeyId(primaryKey.toString());

        workflowStartRequest.setWorkflowStartType(WorkflowStartTypeEnum.API_START.getCode());

        workflowStartRequest.setDefinitionId(flowDefinitionId);

        workflowStartRequest.setApplicant(LoginContext.me().getLoginUser().getUserId());
        workflowStartRequest.setApplicantOrgId(LoginContext.me().getLoginUser().getCurrentOrgId());
        workflowStartRequest.setApplicantPositionId(LoginContext.me().getLoginUser().getCurrentPositionId());

        workflowControlApi.start(workflowStartRequest);
    }

}
