package cn.edu.whu.boot.collection.controller;

import cn.edu.whu.boot.allocation.services.TaskCellService;
import cn.edu.whu.boot.allocation.services.TaskUserRoleService;
import cn.edu.whu.boot.collection.dto.CompositeKey;
import cn.edu.whu.boot.collection.service.IBaseService;
import cn.edu.whu.boot.common.domain.CommonConstant;
import cn.edu.whu.boot.common.domain.ResultMessage;
import cn.edu.whu.boot.common.domain.SqlStringConstant;
import cn.edu.whu.boot.common.utils.collection.OtherUtil;
import cn.edu.whu.boot.common.utils.collection.PostgreSqlUtil;
import cn.edu.whu.boot.common.utils.collection.TableOperationUtil;
import cn.edu.whu.boot.common.utils.xml.CheckTableDataUtil;
import cn.edu.whu.boot.common.utils.xml.GenerateSqlUtil;
import cn.edu.whu.boot.user.entities.User;
import cn.edu.whu.boot.user.services.UserService;
import cn.edu.whu.boot.xml.domain.Task;
import cn.edu.whu.boot.xml.service.ITaskService;
import cn.edu.whu.boot.xml.xmlreader.bean.Config;
import cn.edu.whu.boot.xml.xmlreader.bean.Table;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.text.MessageFormat;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 这个类里面放的东西很杂，写的不规范!!! 具体每个方法的作用看方法的注释
 */
@RestController
@RequestMapping("base")
public class BaseController{

    private static final Logger logger = (Logger) LoggerFactory.getLogger(BaseController.class);
    private final ITaskService taskService;
    private final IBaseService baseService;
    private final UserService userService;
    private final TaskUserRoleService taskUserRoleService;
    private final TaskCellService taskCellService;

    @Autowired
    public BaseController(ITaskService taskService, IBaseService baseService, UserService userService,
                          TaskUserRoleService taskUserRoleService, TaskCellService taskCellService){
        this.taskService = taskService;
        this.baseService = baseService;
        this.userService = userService;
        this.taskUserRoleService = taskUserRoleService;
        this.taskCellService = taskCellService;
    }
    /**
     * 接受移动端传来的数据和多媒体文件，并将数据保存在数据库中，多媒体文件保存在本地!
     *
     * @param detail 按指定格式封装的 JsonString
     * @param file   多媒体压缩包，注意多媒体文件也可以上传
     */
    @RequestMapping(value = "insertAndRecheckData", produces = "text/html;charset=UTF-8")
    public String insertAndRecheckData(String detail, MultipartFile file) throws Exception {
        try {
            JSONObject jsonObject = JSON.parseObject(detail);
            JSONObject tableJsonObject = jsonObject.getJSONObject("table");

            // 预处理JSON中的boolean值
            preprocessBooleanValues(tableJsonObject);

            Table table = JSON.parseObject(tableJsonObject.toJSONString(), Table.class);

//            Table table = JSON.toJavaObject(tableJsonObject, Table.class);
            Integer taskId = jsonObject.getInteger("taskId");
            // 判断当前任务是否存在
            Task task = taskService.getTaskById(taskId);
            if (task == null) {
                return ResultMessage.failed("当前任务已删除!").toString();
            }

            // 判断该用户是否在对应的任务中
            int userId = jsonObject.getIntValue("userId");
            if(!OtherUtil.isUserInTask(task, userId, taskUserRoleService)){
                return ResultMessage.failed("您不在该任务中!").toString();
            }

            table = TableOperationUtil.changeJsonObjectToField(table);
            JSONArray multimediaItemJsonArray =
                    (JSONArray) TableOperationUtil.getFieldValue(table, TableOperationUtil.getCameraFieldName(table));
            // polygonIdFromAndroid 为移动端传入的数据主键，polygonId 是返回给移动端的数据主键
            String primaryKey = TableOperationUtil.getPrimaryKey(table);
            Object polygonIdStringFromAndroid = TableOperationUtil.getFieldValue(table, primaryKey);
            Long polygonIdFromAndroid = null;
            if(polygonIdStringFromAndroid != null){
                polygonIdFromAndroid = Long.parseLong(polygonIdStringFromAndroid.toString());
            }
            Long polygonId;


            CompositeKey compositeKey = new CompositeKey();
            String trueTableName = PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), table.getKey(),
                    false, false);

            // 情况1 ： polygonIdFromAndroid == null，新采集的数据
            if(polygonIdFromAndroid == null){
                polygonId = baseService.insertData(task, table, userId, multimediaItemJsonArray, file);
                compositeKey.setPolygonId(polygonId);
                compositeKey.setVersion(baseService.getVersionByLongId(trueTableName,
                        PostgreSqlUtil.transformString(primaryKey),
                        polygonId));
            }
            // 情况2 ： polygonIdFromAndroid != null ，说明不是新采集的数据，之前采集过一次!
            // 说明这条数据为复核数据(数据上传错了，重新上传也归为复核)
            else{
                Integer versionCodeInTable = baseService.getVersionByLongId(trueTableName,
                        PostgreSqlUtil.transformString(primaryKey),
                        polygonIdFromAndroid);
                if(versionCodeInTable == null){
                    return ResultMessage.failed("数据库中无对应数据!").toString();
                }
                // versionCode 为复核上传的记录版本，versionCodeInTable 为根表中记录版本
                int version = (int) TableOperationUtil.getFieldValue(table, CommonConstant.FIX_FIELD_VERSION);
                if(versionCodeInTable > version){
                    return ResultMessage.failed(8888, "版本号过低").toString();
                }
                // 上传版本号 >= 根表中记录版本，进行更新根表中数据版本(根表中数据更新，像多媒体表和关联表的数据先删除再插入)
                // 在上述操作之前必须将旧版本数据放在历史数据库中
                polygonIdFromAndroid = baseService.updateRecheckData(task, table, userId,
                        multimediaItemJsonArray, polygonIdFromAndroid, file);
                compositeKey.setPolygonId(polygonIdFromAndroid);
                compositeKey.setVersion(baseService.getVersionByLongId(trueTableName,
                        PostgreSqlUtil.transformString(primaryKey),
                        polygonIdFromAndroid));
            }
            return ResultMessage.success(compositeKey).toString();
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("数据处理失败: {}", e.getMessage());
            return ResultMessage.failed("数据格式错误: " + e.getMessage()).toString();
        }
    }
    /**
     * 预处理JSON中的boolean值，将表达式转换为实际的boolean值
     */
    public void preprocessBooleanValues(Map<String, Object> data) {
        if (data == null) {
            return;
        }

        for (Map.Entry<String, Object> entry : data.entrySet()) {
            Object value = entry.getValue();
            if (value instanceof Map) {
                preprocessBooleanValues((Map<String, Object>) value);
            } else if (value instanceof String) {
                String strValue = (String) value;
                try {
                    // 处理特殊表达式情况
                    if (strValue.contains("length==")) {
                        // 简单处理长度检查表达式
                        boolean isEmpty = strValue.contains("length==0");
                        entry.setValue(isEmpty);
                        logger.info("Converted expression '{}' to {}", strValue, isEmpty);
                    }
                    // 处理标准布尔值
                    else if ("true".equalsIgnoreCase(strValue)) {
                        entry.setValue(true);
                    } else if ("false".equalsIgnoreCase(strValue)) {
                        entry.setValue(false);
                    }
                } catch (Exception e) {
                    logger.warn("Failed to parse value '{}': {}", strValue, e.getMessage());
                    // 保持原始值不变
                }
            }
        }
    }

    /**
     * 将某个根表下某数据回退到某个历史版本
     *
     * @param taskId 任务id
     * @param tableName 表名
     * @param polygonId 数据主键id
     * @param version 版本号
     */
    @RequestMapping(value = "rollBackDataForVersion", produces = "text/html;charset=UTF-8")
    public String rollBackDataForVersion(Integer taskId, String tableName, Long polygonId,
                                         Integer version) throws Exception{
        Task task = taskService.getTaskById(taskId);
        Config config = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class);
        Map<String, Table> tablesMap = config.getTables();
        Table table = tablesMap.get(tableName);
        String primaryKey = TableOperationUtil.getPrimaryKey(table);
        String selectSql =
                GenerateSqlUtil.genSqlByPolygonIdAndVersionId(task.getDatabaseName(),
                                                              tableName + CommonConstant.HISTORY_SUFFIX,
                                                              primaryKey, polygonId, version);

        // 获取根表数据中主键字段值为polygonId，version 字段为 versionId 的记录，然后进行回滚更新
        List<Map<String, Object>> rootTableDataList = baseService.selectData(selectSql);
        ResultMessage resultMessage = OtherUtil.checkDataListGetByPolygonId(rootTableDataList);
        if(!resultMessage.isSuccess()){
            return resultMessage.toString();
        }
        String updateSqlForRootTable = GenerateSqlUtil.generateUpdateSqlByData(rootTableDataList.get(0),
                                                                               task.getDatabaseName(), tableName,
                                                                               primaryKey, polygonId, task.getConfig());
        String result = baseService.rollBackDataForVersion(task, table, updateSqlForRootTable, polygonId, version);
        if(result.equals(CommonConstant.SUCCEED)){
            return ResultMessage.success(null).toString();
        }
        else{
            return ResultMessage.failed(null).toString();
        }
    }


    /**
     * 指定某个数据审核通过，或者审核未通过! 如果审核未通过需要提供原因
     * @param taskId     任务 id
     * @param tableName  表名
     * @param polygonId  数据主键
     * @param pass       审核是否通过
     * @param reason     审核未通过原因
     */
    @RequestMapping(value = "setDataStatus", produces = "text/html;charset=UTF-8")
    public String setDataStatus(Integer taskId, String tableName, Long polygonId, boolean pass, String reason){
        Task task = taskService.getTaskById(taskId);
        Config config = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class);
        Map<String, Table> tablesMap = config.getTables();
        Table table = tablesMap.get(tableName);
        String tableNameInDb = PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), tableName, false, false);
        int status;
        if(pass){
            status = 2;
            reason = null;
        }
        else{
            status = -1;
        }
        baseService.updateTableStatus(tableNameInDb, PostgreSqlUtil.transformString(TableOperationUtil.getPrimaryKey(table)),
                                      polygonId, status, reason);
        return ResultMessage.success(null).toString();
    }

    /**
     * 根据某任务的统计时间，进行每天的数据统计，并返回给前端! 该接口也就是网页端的“异常监管”!
     * 需要将异常范围的上下边界两个参数值提供出来
     * @param taskId 任务 id
     * @param tableName 表名， 注意如果 tableName 是 "" ，表明需要统计所有的根表
     * @param lowLimit 下边界
     * @param highLimit 上边界
     */
    @RequestMapping(value = "getWarningForUser", produces = "text/html;charset=UTF-8")
    public String getCountForUserEveryday(Integer taskId, String tableName, double lowLimit, double highLimit){
        List<User> workers = taskService.getWorkersByTask(taskId);
        if (workers.isEmpty()) {
            return new ResultMessage<>(true, "任务下无外业人员").toString();
        }

        Set<String> tableNameSet;
        List<Map<String, Object>> unusualData = new ArrayList<>();
        // yearMonthDayList 表示采集数据的年月日，对任务中每个根表进行统计
        List<String> yearMonthDayList = new ArrayList<>();
        // 不区分巡查员和采集员，放在一起计算工作量!
        Task task = taskService.getTaskById(taskId);
        Long allDataCount = 0L;
        if("mmoo".equals(tableName)){
            tableNameSet = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getRootTables().keySet();
        }
        else{
            tableNameSet = new HashSet<>();
            tableNameSet.add(tableName);
        }
        for(String rootTableName : tableNameSet){
            String rootTableNameInDb = PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), rootTableName, false, false);
            allDataCount += baseService.getHasCollectAndPatrolDataCount(rootTableNameInDb);
            String selectYearMonthDaySql = MessageFormat.format("select DISTINCT(to_char(update_time,{1})) as collect_date from {0}" +
                                                                        "where status = 1 or status = 2 " +
                                                                        "ORDER BY collect_date",
                                                                rootTableNameInDb, "\'yyyy-MM-dd\'");
            List<Map<String, Object>> yearMonthDayResult = baseService.selectData(selectYearMonthDaySql);
            if(yearMonthDayResult != null && yearMonthDayResult.size() != 0){
                for(Map<String, Object> yearMonthDay : yearMonthDayResult){
                    if(!yearMonthDayList.contains(yearMonthDay.get("collect_date").toString())){
                        yearMonthDayList.add(yearMonthDay.get("collect_date").toString());
                    }
                }
            }
        }

        yearMonthDayList.sort(Comparator.naturalOrder());
        // 判断 yearMonthDayList 是否有当天的日期，若有将它排除,以及 yearMonthDayList 为空就返回
        if(yearMonthDayList.size() == 0){
            return new ResultMessage<>(true, "任务下无数据").toString();
        }
        // 从数据中获取的平均工作量
        int avgCollectionNum = (int)(allDataCount / workers.size() / yearMonthDayList.size()),
            lowLimitNum = (int)(avgCollectionNum * lowLimit),
            highLimitNum = (int)(avgCollectionNum * highLimit);

        // 获取任务中的数据
        for(User worker : workers){
            Map<String, Object> tempMap = new HashMap<>();
            Optional<User> userOptional = userService.findById(worker.getId());
            // 获取该外业人员异常日期及该日期下对应的工作量
            Map<String, Long> dataCountForDayMap = new HashMap<>();
            for(String rootTableName : tableNameSet){
                String rootTableNameInDb = PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), rootTableName, false, false);
                List<Map<String, Object>> dataCountAndTimeList = baseService.getDataCountAndTimeByUser(rootTableNameInDb, worker.getId());
                for(Map<String, Object> dataCountAndTimeMap : dataCountAndTimeList){
                    String time = dataCountAndTimeMap.get("updateTime").toString();
                    Long countValue = Long.valueOf(dataCountAndTimeMap.get("dataCount").toString());
                    if(!dataCountForDayMap.containsKey(time)){
                        dataCountForDayMap.put(time, countValue);
                    }
                    else{
                        Long dataCountAdd = dataCountForDayMap.get(time) + countValue;
                        dataCountForDayMap.remove(time);
                        dataCountForDayMap.put(time, dataCountAdd);
                    }
                }
            }
            List<Map<String, Object>> unusualDayList = new ArrayList<>();
            for(String yearMonthDay : yearMonthDayList){
                if(!dataCountForDayMap.containsKey(yearMonthDay)){
                    Map<String, Object> unusualDayMap = new HashMap<>();
                    unusualDayMap.put("day", yearMonthDay);
                    unusualDayMap.put("num", 0);
                    unusualDayList.add(unusualDayMap);
                }
                else if(dataCountForDayMap.get(yearMonthDay) < lowLimitNum
                        || dataCountForDayMap.get(yearMonthDay) > highLimitNum){
                    Map<String, Object> unusualDayMap = new HashMap<>();
                    unusualDayMap.put("day", yearMonthDay);
                    unusualDayMap.put("num", dataCountForDayMap.get(yearMonthDay));
                    unusualDayList.add(unusualDayMap);
                }
            }
            if(unusualDayList.size() != 0){
                tempMap.put("unusualDay", unusualDayList);
                tempMap.put("userName", userOptional.get().getUsername());
                unusualData.add(tempMap);
            }
        }
        Map<String, Object> result = new HashMap<>();
        result.put("unusualData", unusualData);
        result.put("averageNum", avgCollectionNum);
        result.put("lowLimitNum", lowLimitNum);
        result.put("highLimitNum", highLimitNum);
        return ResultMessage.success(result).toString();
    }

    /**
     * 根据前端传入的任务 taskId 及经纬度范围进行数据抽稀，只返回某范围的数据个数
     * accuracy、longitudeMin、latitudeMin、longitudeMax、latitudeMax
     * 缩放精度、  最小经度  、  最小纬度 、  最大经度  、  最大纬度
     */
    @RequestMapping(value = "getCountByGeoHash", produces = "text/html;charset=UTF-8")
    public String getCountByGeoHash(Integer taskId, String tableName, Integer accuracy, Double longitudeMin,
                                    Double latitudeMin, Double longitudeMax, Double latitudeMax){
        Task task = taskService.getTaskById(taskId);
        Table table = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getTables().get(tableName);
        // 获取几何字段名
        String geometryFieldName = CheckTableDataUtil.getGeometryFieldName(table);

        // 如果没有几何字段，直接返回空
        if (geometryFieldName == null) {
            return ResultMessage.success(null).toString();
        }
        String tableNameInDb = PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), tableName, false, false);

        String selectSql = MessageFormat.format(SqlStringConstant.SELECT_SQL_BY_GEOHASH,
                                                tableNameInDb,
                                                PostgreSqlUtil.transformString(CheckTableDataUtil.getGeometryFieldName(table)),
                                                accuracy, longitudeMin, latitudeMin, longitudeMax, latitudeMax, "");
        List<Map<String, Object>> dataList = baseService.selectData(selectSql);
        if(dataList != null && dataList.size() != 0){
            List<Map<String, Object>> result = OtherUtil.genFeatureCountList(dataList);
            return ResultMessage.success(result).toString();
        }
        else{
            return ResultMessage.success(null).toString();
        }
    }

    /**
     * 获取热力图相关的数据
     * @param taskId     任务 id
     * @param tableName  表名
     */
    @RequestMapping(value = "getHeatData", produces = "text/html;charset=UTF-8")
    public String getHeatData(Integer taskId, String tableName){
        Map<String, Object> coordinateRangeMap = taskCellService.getCoordinateRange(taskId);
        if(coordinateRangeMap != null && coordinateRangeMap.size() != 0){
            Task task = taskService.getTaskById(taskId);
            Table table = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getTables().get(tableName);
            String tableNameInDb = PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), tableName, false, false);
            String selectSql = MessageFormat.format(SqlStringConstant.SELECT_CENTROID_SQL_BY_GEOHASH,
                                                    tableNameInDb,
                                                    PostgreSqlUtil.transformString(CheckTableDataUtil.getGeometryFieldName(table)), 7,
                                                    Double.valueOf(coordinateRangeMap.get("xmin").toString()),
                                                    Double.valueOf(coordinateRangeMap.get("ymin").toString()),
                                                    Double.valueOf(coordinateRangeMap.get("xmax").toString()),
                                                    Double.valueOf(coordinateRangeMap.get("ymax").toString()));
            List<Map<String, Object>> dataList = baseService.selectData(selectSql);
            if(dataList != null && dataList.size() != 0){
                List<Map<String, Object>> result = OtherUtil.genFeatureCountList(dataList);
                return ResultMessage.success(result).toString();
            }
        }
        return ResultMessage.success("null").toString();
    }

    /**
     * 根据前端传入的任务 taskId，tableName 及中心点，获取据该中心点小于 100m 的数据
     */
    @RequestMapping(value = "getDataByDistance", produces = "text/html;charset=UTF-8")
    public String getDataByDistance(Integer taskId, String tableName, Double longitude, Double latitude){
        Task task = taskService.getTaskById(taskId);
        Table table = JSON.toJavaObject(JSON.parseObject(task.getConfig()), Config.class).getTables().get(tableName);
        String tableNameInDb = PostgreSqlUtil.buildTableNameString(task.getDatabaseName(), tableName, false, false);
        String pointGeoJson = "\'{" + MessageFormat.format(SqlStringConstant.POINT_GEOGSON, longitude, latitude) + "}\'";
        String selectSql = MessageFormat.format(SqlStringConstant.SELECT_SQL_BY_DISTANCE, tableNameInDb,
                                                PostgreSqlUtil.transformString(CheckTableDataUtil.getGeometryFieldName(table)), pointGeoJson);
        List<Map<String, Object>> dataList = baseService.selectData(selectSql);
        String primaryKey = TableOperationUtil.getPrimaryKey(table);
        if(dataList != null && dataList.size() != 0){
            List<Long> result = new ArrayList<>();
            dataList.forEach(data -> result.add(Long.valueOf(data.get(primaryKey).toString())));
            return ResultMessage.success(result).toString();
        }
        else{
            return ResultMessage.success(null).toString();
        }
    }
}
