package com.yice.webadmin.app.controller;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.page.PageMethod;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.yice.common.core.annotation.MyRequestBody;
import com.yice.common.core.constant.ErrorCodeEnum;
import com.yice.common.core.object.*;
import com.yice.common.core.util.MyCommonUtil;
import com.yice.common.core.util.MyModelUtil;
import com.yice.common.core.util.MyPageUtil;
import com.yice.common.log.annotation.OperationLog;
import com.yice.common.log.model.constant.SysOperationLogType;
import com.yice.webadmin.app.config.LlmModelConfig;
import com.yice.webadmin.app.config.OtherConfig;
import com.yice.webadmin.app.config.PythonConfig;
import com.yice.webadmin.app.constant.ModelConstant;
import com.yice.webadmin.app.dto.ModelInstanceDto;
import com.yice.webadmin.app.model.ModelInstance;
import com.yice.webadmin.app.model.ModelService;
import com.yice.webadmin.app.model.ModelVersion;
import com.yice.webadmin.app.service.ModelInstanceService;
import com.yice.webadmin.app.service.ModelServiceService;
import com.yice.webadmin.app.service.ModelVersionService;
import com.yice.webadmin.app.service.ProxyPythonService;
import com.yice.webadmin.app.vo.ModelInstanceVo;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

/**
 * 模型实例管理操作控制器类。
 *
 * @author linking
 * @date 2023-04-13
 */
@Api(tags = "模型实例管理接口")
@Slf4j
@RestController
@RequestMapping("/admin/app/modelInstance")
public class ModelInstanceController {

    @Autowired
    private ModelInstanceService modelInstanceService;
    @Autowired
    private ProxyPythonService proxyPythonService;
    @Autowired
    private OtherConfig otherConfig;
    @Autowired
    private LlmModelConfig llmModelConfig;
    @Autowired
    private PythonConfig pythonConfig;
    @Autowired
    private ModelVersionService modelVersionService;
    @Autowired
    private ModelServiceService modelServiceService;

    /**
     * 新增模型实例管理数据。
     *
     * @param modelInstanceDto 新增对象。
     * @return 应答结果对象，包含新增对象主键Id。
     */
    @ApiOperationSupport(ignoreParameters = {"modelInstanceDto.instanceId"})
    @OperationLog(type = SysOperationLogType.ADD)
    @PostMapping("/add")
    public ResponseResult<Long> add(@MyRequestBody ModelInstanceDto modelInstanceDto) {
        String errorMessage = MyCommonUtil.getModelValidationError(modelInstanceDto, false);
        if (errorMessage != null) {
            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
        }
        ModelInstance instance = MyModelUtil.copyTo(modelInstanceDto, ModelInstance.class);
        CallResult callResult = modelInstanceService.verifyRelatedData(instance, null);
        if (!callResult.isSuccess()) {
            return ResponseResult.errorFrom(callResult);
        }
        ModelInstance filter = new ModelInstance();
        filter.setVersionId(modelInstanceDto.getVersionId());
        if(modelInstanceService.getCountByFilter(filter) > 0) {
            return ResponseResult.error(ErrorCodeEnum.DUPLICATED_UNIQUE_KEY, "同一个模型只能部署一次！");
        }
        ModelInstance modelInstance = MyModelUtil.copyTo(modelInstanceDto, ModelInstance.class);
        modelInstanceService.saveNew(modelInstance);
        return ResponseResult.success(modelInstance.getInstanceId());
    }

    /**
     * 停止指定LLM模型。
     *
     * @return 应答结果对象，包含查询结果集。
     */
    @PostMapping("/stop")
    public ResponseResult<String> stop(@MyRequestBody ModelInstanceDto modelInstanceDto) {
        ModelInstance modelInstance = modelInstanceService.getById(modelInstanceDto.getInstanceId());
        ResponseResult<String> responseResult = this.doStop(modelInstance);
        if (responseResult.isSuccess()) {
            modelInstance.setDeployStatus(3);
            modelInstanceService.updateById(modelInstance);
            return ResponseResult.success(responseResult.getData());
        }
        return responseResult;
    }

    /**
     * 部署指定LLM模型。
     *
     * @return 应答结果对象，包含查询结果集。
     */
    @PostMapping("/deploy")
    public ResponseResult<String> deploy(@MyRequestBody ModelInstanceDto modelInstanceDto,
                                         @MyRequestBody String type) {
        ModelInstance modelInstance = modelInstanceService.getById(modelInstanceDto.getInstanceId());
        modelInstance.setDeployStatus(ModelConstant.DEPLOY_STATUS_DEPLOYED);
        modelInstanceService.updateById(modelInstance);
        ResponseResult<String> responseResult = this.doReloadOrStart(modelInstance, type);
        if (responseResult.isSuccess()) {
            modelInstance.setDeployStatus(ModelConstant.DEPLOY_STATUS_COMPLETE);
            modelInstanceService.updateById(modelInstance);
            return ResponseResult.success(responseResult.getData());
        }
        modelInstance.setDeployStatus(ModelConstant.DEPLOY_STATUS_FAILURE);
        modelInstanceService.updateById(modelInstance);
        return responseResult;
    }

    /**
     * 调用Python模型方法
     * @param modelInstance 模型实例
     * @param type 类型
     * @return 消息
     */
    private ResponseResult<String> doReloadOrStart(ModelInstance modelInstance, String type) {
        ModelService filter = new ModelService();
        List<ModelService> modelServiceList = modelServiceService.getModelServiceList(filter, null);
        if(CollUtil.isEmpty(modelServiceList)) return null;

        String resourceInfo = modelServiceList.get(0).getResourceInfo();
        String gps_ids = JSON.parseArray(resourceInfo).stream()
                .map(obj -> ((JSONObject) obj).getString("gpu_id"))
                .collect(Collectors.joining(","));
        ModelVersion modelVersion = this.modelVersionService.getById(modelInstance.getVersionId());
        String requestBody = "{\n" +
                "  \"new_model_name\": \"" + modelInstance.getVersionName() + "\",\n" +
                "  \"new_model_path\": \"" + modelVersion.getModelUrl() + "\",\n" +
                "  \"startup_param\": {\n" +
                "    \"gpu_ids\": \"" + gps_ids + "\"\n" +
                "  },\n" +
                "  \"controller_address\": \"" + pythonConfig.getControllerAddress() + "\"\n" +
                "}";
        try {
            String apiName = type.equals("start") ? llmModelConfig.getStart() : llmModelConfig.getReload();
            String result = proxyPythonService.predictPost(llmModelConfig.getLlmModelInterface() + apiName, requestBody);
            JSONObject jo = JSON.parseObject(result);
            Integer code = jo.getIntValue("code");
            String msg = jo.getString("msg");
            String data = jo.getString("data");
            if (code != null && code == 200) {
                return ResponseResult.success(data);
            } else {
                return ResponseResult.create(ErrorCodeEnum.SERVER_INTERNAL_ERROR, msg, data);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 模型停止，调用python接口
     * @param modelInstance 模型实例
     * @return 消息
     */
    private ResponseResult<String> doStop(ModelInstance modelInstance) {
        String errorMessage = MyCommonUtil.getModelValidationError(modelInstance, true);
        if (errorMessage != null) {
            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
        }
        String requestBody = "{\n" +
                "  \"model_name\": \"" + modelInstance.getVersionName() + "\",\n" +
                "  \"controller_address\": \"" + pythonConfig.getControllerAddress() + "\"\n" +
                "}";
        try {
            String result = proxyPythonService.predictPost(llmModelConfig.getLlmModelInterface() + llmModelConfig.getStop(), requestBody);
            JSONObject jo = JSON.parseObject(result);
            Integer code = jo.getIntValue("code");
            String msg = jo.getString("msg");
            String data = jo.getString("data");
            if (code != null && code == 200) {
                return ResponseResult.success(data);
            } else {
                return ResponseResult.create(ErrorCodeEnum.SERVER_INTERNAL_ERROR, msg, data);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 列出符合过滤条件的应用接入列表。
     *
     * @return 应答结果对象，包含查询结果集。
     */
    @PostMapping("/load")
    public ResponseResult<List<ModelInstanceVo>> load(@MyRequestBody ModelInstanceDto modelInstanceDtoFilter) {
        ModelInstance modelInstanceFilter = MyModelUtil.copyTo(modelInstanceDtoFilter, ModelInstance.class);
        List<ModelInstance> modelInstanceList = modelInstanceService.getModelInstanceList(modelInstanceFilter, "");
        return ResponseResult.success(ModelInstance.INSTANCE.fromModelList(modelInstanceList));
    }

    /**
     * 获取GPU信息。
     *
     * @return 应答结果对象，包含查询结果集。
     */
    @PostMapping("/getGpuInfo")
    public ResponseResult<String> getGpuInfo() {
        try {
            String result = proxyPythonService.predictPost(
                    otherConfig.getOtherInterface() + otherConfig.getGetGpuInfo(), "");
            JSONObject jo = JSON.parseObject(result);
            Integer code = jo.getIntValue("code");
            String msg = jo.getString("msg");
            String data = jo.getString("data");
            if (code != null && code == 200) {
                return ResponseResult.success(data);
            } else {
                return ResponseResult.create(ErrorCodeEnum.SERVER_INTERNAL_ERROR, msg, data);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 更新模型实例管理数据。
     *
     * @param modelInstanceDto 更新对象。
     * @return 应答结果对象。
     */
    @OperationLog(type = SysOperationLogType.UPDATE)
    @PostMapping("/update")
    public ResponseResult<Void> update(@MyRequestBody ModelInstanceDto modelInstanceDto) {
        String errorMessage = MyCommonUtil.getModelValidationError(modelInstanceDto, true);
        if (errorMessage != null) {
            return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
        }
        ModelInstance modelInstance = MyModelUtil.copyTo(modelInstanceDto, ModelInstance.class);
        ModelInstance originalModelInstance = modelInstanceService.getById(modelInstance.getInstanceId());
        if (originalModelInstance == null) {
            // NOTE: 修改下面方括号中的话述
            errorMessage = "数据验证失败，当前 [数据] 并不存在，请刷新后重试！";
            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
        }
        if (!modelInstanceService.update(modelInstance, originalModelInstance)) {
            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
        }
        return ResponseResult.success();
    }

    /**
     * 删除模型实例管理数据。
     *
     * @param instanceId 删除对象主键Id。
     * @return 应答结果对象。
     */
    @OperationLog(type = SysOperationLogType.DELETE)
    @PostMapping("/delete")
    public ResponseResult<Void> delete(@MyRequestBody Long instanceId) {
        if (MyCommonUtil.existBlankArgument(instanceId)) {
            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
        }
        return this.doDelete(instanceId);
    }

    /**
     * 批量删除模型实例管理数据。
     *
     * @param instanceIdList 待删除对象的主键Id列表。
     * @return 应答结果对象。
     */
    @OperationLog(type = SysOperationLogType.DELETE_BATCH)
    @PostMapping("/deleteBatch")
    public ResponseResult<Void> deleteBatch(@MyRequestBody List<Long> instanceIdList) {
        if (MyCommonUtil.existBlankArgument(instanceIdList)) {
            return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
        }
        for (Long instanceId : instanceIdList) {
            ResponseResult<Void> responseResult = this.doDelete(instanceId);
            if (!responseResult.isSuccess()) {
                return responseResult;
            }
        }
        return ResponseResult.success();
    }

    /**
     * 列出符合过滤条件的模型实例管理列表。
     *
     * @param modelInstanceDtoFilter 过滤对象。
     * @param orderParam 排序参数。
     * @param pageParam 分页参数。
     * @return 应答结果对象，包含查询结果集。
     */
    @PostMapping("/list")
    public ResponseResult<MyPageData<ModelInstanceVo>> list(
            @MyRequestBody ModelInstanceDto modelInstanceDtoFilter,
            @MyRequestBody MyOrderParam orderParam,
            @MyRequestBody MyPageParam pageParam) {
        if (pageParam != null) {
            PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
        }
        ModelInstance modelInstanceFilter = MyModelUtil.copyTo(modelInstanceDtoFilter, ModelInstance.class);
        String orderBy = MyOrderParam.buildOrderBy(orderParam, ModelInstance.class);
        List<ModelInstance> modelInstanceList =
                modelInstanceService.getModelInstanceListWithRelation(modelInstanceFilter, orderBy);
        return ResponseResult.success(MyPageUtil.makeResponseData(modelInstanceList, ModelInstance.INSTANCE));
    }

    /**
     * 查看指定模型实例管理对象详情。
     *
     * @param instanceId 指定对象主键Id。
     * @return 应答结果对象，包含对象详情。
     */
    @GetMapping("/view")
    public ResponseResult<ModelInstanceVo> view(@RequestParam Long instanceId) {
        ModelInstance modelInstance = modelInstanceService.getByIdWithRelation(instanceId, MyRelationParam.full());
        if (modelInstance == null) {
            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST);
        }
        ModelInstanceVo modelInstanceVo = MyModelUtil.copyTo(modelInstance, ModelInstanceVo.class);
        return ResponseResult.success(modelInstanceVo);
    }

    /**
     * 模型实例标识
     * @param instanceId 实例ID
     * @return 响应消息
     */
    private ResponseResult<Void> doDelete(Long instanceId) {
        String errorMessage;
        // 验证关联Id的数据合法性
        ModelInstance originalModelInstance = modelInstanceService.getById(instanceId);
        if (originalModelInstance == null) {
            errorMessage = "数据验证失败，当前 [对象] 并不存在，请刷新后重试！";
            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
        }
        if (!modelInstanceService.remove(instanceId)) {
            errorMessage = "数据操作失败，删除的对象不存在，请刷新后重试！";
            return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage);
        }
        return ResponseResult.success();
    }
}
