package com.xydl.cac.service.impl;

import com.xydl.cac.entity.*;
import com.xydl.cac.entity.constants.Constants;
import com.xydl.cac.model.ColumnModel;
import com.xydl.cac.model.ConditionModel;
import com.xydl.cac.model.OnePage;
import com.xydl.cac.repository.ModevTypeRepository;
import com.xydl.cac.service.DataService;
import com.xydl.cac.util.DataTable;
import com.xydl.cac.util.DataUtil;
import com.xydl.cac.util.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import static com.xydl.cac.entity.constants.Constants.I2BatchSize;

@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class DataServiceImpl implements DataService {

    private String schema;

    @Resource
    private JdbcTemplate jdbcTemplate;
    @Resource
    ModevTypeRepository modevTypeRepository;

    @Override
    public List<ColumnModel> getDataTables() throws Exception {
        List<ModevType> typeList = modevTypeRepository.findAll();
        List<ColumnModel> tables = new ArrayList<>();
        String sql = "SHOW TABLES";
        List<String> list = jdbcTemplate.queryForList(sql, String.class);
        for (String name : list) {
            if (name.startsWith("data_")) {
                ColumnModel item = new ColumnModel();
                item.setName(name);
                for (ModevType type : typeList) {
                    if (name.equals(type.getTablename())) {
                        item.setComment(type.getMc());
                        break;
                    }
                }
                tables.add(item);
            }
        }
        return tables;
    }

    @Override
    public List<ColumnModel> getDataTableColumns(String tableName) {
        if (StringUtils.isBlank(schema)) {
            this.getDatabase();
        }

        List<ColumnModel> result = new ArrayList<>();
        String sql = "SELECT COLUMN_NAME name, COLUMN_COMMENT comment, DATA_TYPE type FROM information_schema.columns WHERE TABLE_NAME='"
                + tableName + "' AND TABLE_SCHEMA='" + schema + "' ORDER BY name,comment";
        List<ColumnModel> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ColumnModel.class));
        for (ColumnModel item : list) {
            String col = item.getName();
            if (!col.equals("id") && !col.equals("eqmid")
                    && !col.equals("acquisitionTime") && !col.equals("d_time")
                    && !col.equals("create_time") && !col.equals("update_time")
                    && !col.equals("isupload") && !col.equals("capturetime")) {
                result.add(item);
            }
        }
        return result;
    }

    @Cacheable(value = "timeField", key = "#tableName")
    public String getTimeField(String tableName) {
        if (StringUtils.isBlank(schema)) {
            this.getDatabase();
        }
        String field = "d_time";
        String sql = "SELECT COLUMN_NAME name, COLUMN_COMMENT comment, DATA_TYPE type FROM information_schema.columns WHERE TABLE_NAME='"
                + tableName + "' AND TABLE_SCHEMA='" + schema + "' ORDER BY name desc";
        List<ColumnModel> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ColumnModel.class));
        for (ColumnModel item : list) {
            String col = item.getName();
            if (col.equals("acquisitionTime") || col.equals("d_time")
                    || col.equals("capturetime")) {
                field = col;
                break;
            }
        }
        return field;
    }

    @Cacheable(value = "fieldType", key = "#tableName + '-' + #field")
    public String getFieldType(String tableName, String field) {
        String type = "";
        List<ColumnModel> columnList = this.getDataTableColumns(tableName);
        for (ColumnModel col : columnList) {
            if (col.getName().equals(field)) {
                type = col.getType();
                break;
            }
        }
        return type;
    }

    private void getDatabase() {
        String sql = "select database()";
        List<String> list = jdbcTemplate.queryForList(sql, String.class);
        schema = list.get(0);
    }


    @Override
    public OnePage<Map<String, Object>> getData(String tableName, Integer devId, List<ModevTypePoint> points, ConditionModel model) throws Exception {
        if (model.getNumPerDay() != null && model.getNumPerDay() > 0) {
            OnePage<Map<String, Object>> page;
            long totalDays = 0;
            // 需要抽取数据
            if (model.getPageNum() != null || model.getPageSize() != null) {

                // 每页天数
                int dayPerPage = model.getPageSize() / model.getNumPerDay();
                if (dayPerPage < 1) {
                    dayPerPage = 1;
                }
                ConditionModel newmodel = new ConditionModel();
                newmodel.setTimeAsc(model.isTimeAsc());

                // 结束时间和总天数
                Date end = model.getEndTime();
                if (end == null) {
                    end = new Date();
                }
                end = DateUtil.addDay(end, 1);
                end = DateUtil.getDayZero(end);
                model.setEndTime(end);
                if (model.getStartTime() != null) {
                    totalDays = DateUtil.getDifferenceInDays(model.getStartTime(), end);
                }

                if (model.isTimeAsc()) {
                    // 升序
                    Date start = model.getStartTime();
                    if (model.getPageNum() > 1) {
                        int p = model.getPageNum() - 1;
                        start = DateUtil.addDay(start, dayPerPage * p);
                    }
                    newmodel.setStartTime(start);
                    end = DateUtil.addDay(start, dayPerPage);
                    if (model.getEndTime() == null || end.compareTo(model.getEndTime()) < 0) {
                        newmodel.setEndTime(end);
                    } else {
                        newmodel.setEndTime(model.getEndTime());
                    }
                } else {
                    // 降序
                    if (model.getPageNum() > 1) {
                        int p = model.getPageNum() - 1;
                        end = DateUtil.addDay(end, -dayPerPage * p);
                    }
                    newmodel.setEndTime(end);
                    Date start = DateUtil.addDay(end, -dayPerPage);
                    if (model.getStartTime() == null || start.compareTo(model.getStartTime()) > 0) {
                        newmodel.setStartTime(start);
                    } else {
                        newmodel.setStartTime(model.getStartTime());
                    }
                }

                // 查询
                page = this.doSqlData(tableName, devId, points, newmodel);
                page.setTotal(totalDays * model.getNumPerDay());

            } else {
                // 全量
                page = this.doSqlData(tableName, devId, points, model);
            }

            // 从数据里每天抽取num条
            List<Map<String, Object>> content = DataUtil.pickNumPerDay(page.getContent(), model.getNumPerDay());
            page.setContent(content);

            page.setPageNum(model.getPageNum());
            page.setPageSize(content.size());
            return page;
        } else {
            // 不抽取数据
            OnePage<Map<String, Object>> page = this.doSqlData(tableName, devId, points, model);
            return page;
        }
    }

    private OnePage<Map<String, Object>> doSqlData(String tableName, Integer devId, List<ModevTypePoint> points, ConditionModel model) {
        String devField = DataTable.getDevidField(tableName);
        String timeField = this.getTimeField(tableName);
        OnePage result = new OnePage();

        String sqlFrom = " FROM " + tableName;
        String sqlWhere = " WHERE " + devField + "=" + devId;
        if (model.getStartTime() != null) {
            if (model.isExcludeStartTime()) {
                sqlWhere = sqlWhere + " AND " + timeField + " > '" + DateUtil.format(model.getStartTime()) + "'";
            } else {
                sqlWhere = sqlWhere + " AND " + timeField + " >= '" + DateUtil.format(model.getStartTime()) + "'";
            }
        }
        if (model.getEndTime() != null) {
            sqlWhere = sqlWhere + " AND " + timeField + " < '" + DateUtil.format(model.getEndTime()) + "'";
        }
        String sqlOrder = " ORDER BY " + timeField;
        if (model.isTimeAsc()) {
            sqlOrder = sqlOrder + " ASC";
        } else {
            sqlOrder = sqlOrder + " DESC";
        }
        String sqlLimit = "";
        if (model.getPageNum() != null && model.getPageSize() != null) {
            result.setPageNum(model.getPageNum());
            result.setPageSize(model.getPageSize());
            sqlLimit = String.format(" LIMIT %d,%d", (model.getPageNum() - 1) * model.getPageSize(), model.getPageSize());
        }

        String sqlSelect = "SELECT DATE_FORMAT(" + timeField + ", '%Y-%m-%d %H:%i:%s') as acquisitionTime";
        for (ModevTypePoint point : points) {
            if (StringUtils.isNotBlank(point.getField())) {
                if (Constants.DateTime.equalsIgnoreCase(point.getType())) {
                    sqlSelect = sqlSelect + ", " + "DATE_FORMAT(" + point.getField() + ", '%Y-%m-%d %H:%i:%s') as " + point.getField();
                } else {
                    sqlSelect = sqlSelect + ", " + point.getField();
                }
            }
        }

        String sqlCount = "SELECT count(*)" + sqlFrom + sqlWhere;
        String sql = sqlSelect + sqlFrom + sqlWhere + sqlOrder + sqlLimit;

        Long count = jdbcTemplate.queryForObject(sqlCount, Long.class);
        List<Map<String, Object>> content = jdbcTemplate.queryForList(sql);

        result.setTotal(count);
        result.setContent(content);
        return result;
    }

    @Override
    public void clearAllBind() {
        String sql = "UPDATE n_sensor SET icd_id=null";
        jdbcTemplate.execute(sql);
    }

    @Override
    public List<Map<String, Object>> getLatestData(String tableName, Integer devId, List<ModevTypePoint> points, Date start) throws Exception {
        ConditionModel model = new ConditionModel();
        model.setPageSize(I2BatchSize);
        model.setPageNum(1);
        model.setStartTime(start);
        model.setExcludeStartTime(true);
        model.setTimeAsc(true);

        OnePage<Map<String, Object>> page = this.doSqlData(tableName, devId, points, model);
        return page.getContent();
    }

    @Override
    public Map<String, Object> getLastOneData(String tableName, Integer devId, ModevTypePoint point) throws Exception {
        List<ModevTypePoint> points = new ArrayList<>();
        points.add(point);

        ConditionModel model = new ConditionModel();
        model.setPageSize(1);
        model.setPageNum(1);

        OnePage<Map<String, Object>> page = this.doSqlData(tableName, devId, points, model);
        if (CollectionUtils.isEmpty(page.getContent())) {
            return null;
        } else {
            Map<String, Object> map = page.getContent().get(0);
            return map;
        }
    }

    @Override
    public void insertData(String tableName, Integer devId, String time, String colname, String value) {
        if (StringUtils.isBlank(tableName)) {
            return;
        }
        if (StringUtils.isBlank(colname)) {
            return;
        }
        String devField = DataTable.getDevidField(tableName);
        String timeField = this.getTimeField(tableName);
        String sqlFrom = " FROM " + tableName;
        String sqlWhere = " WHERE " + devField + "=" + devId + " AND " + timeField + " = '" + time + "'";
        String sqlCount = "SELECT count(*)" + sqlFrom + sqlWhere;
        Long count = jdbcTemplate.queryForObject(sqlCount, Long.class);

        String type = this.getFieldType(tableName, colname);
        if (Constants.Varchar.equalsIgnoreCase(type)) {
            value = "'" + value + "'";
        } else if (Constants.Text.equalsIgnoreCase(type)) {
            value = "'" + value + "'";
        } else if (Constants.DateTime.equalsIgnoreCase(type)) {
            BigDecimal bigval = new BigDecimal(value);
            Long ts = bigval.longValue();
            if (ts.toString().length() < 11) {
                ts = ts * 1000;
            }
            Date date = new Date(ts);
            value = DateUtil.format(date);
            value = "'" + value + "'";
        }
        if (count > 0) {
            String sql = "UPDATE " + tableName + " set " + colname + "=" + value
                    + sqlWhere;
            jdbcTemplate.execute(sql);
        } else {
            String sql = "INSERT INTO " + tableName + " (" + devField + ", " + timeField + "," + colname
                    + ") VALUES (" + devId + ", '" + time + "', " + value + ")";
            jdbcTemplate.execute(sql);
        }
    }

}
