package com.torry.data.service;

import com.alibaba.otter.canal.protocol.CanalEntry;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.MongoClientException;
import com.mongodb.MongoSocketException;
import com.torry.data.common.EventData;
import com.torry.data.common.NameConst;
import com.torry.data.config.canal.CanalProperties;
import com.torry.data.util.DBConvertUtil;
import com.torry.data.util.SpringUtil;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Future;

/**
 * DataService,数据处理集合
 *
 * @author zhangtongrui
 * @date 2017/10/13
 */
@Service
public class DataService {
    protected final static Logger logger = LoggerFactory.getLogger(DataService.class);

    @Resource
    MongoTemplate naiveMongoTemplate;
    @Resource
    MongoTemplate completeMongoTemplate;
    @Autowired
    CanalProperties properties;

    public void insert(List<CanalEntry.Column> data, String schemaName, String tableName) {
        DBObject obj = DBConvertUtil.columnToJson(data);
        logger.debug("insert :{}", obj.toString());
        //订单库单独处理
        if (schemaName.equals("order")) {
            //保存原始数据
            if (tableName.startsWith("order_base_info")) {
                tableName = "order_base_info";
            } else if (tableName.startsWith("order_detail_info")) {
                tableName = "order_detail_info";
            } else {
                logger.info("unknown data :{}.{}:{}", schemaName, tableName, obj);
                return;
            }
            insertData(schemaName, tableName, obj, obj);
        } else {
            DBObject newObj = (DBObject) ObjectUtils.clone(obj);
            if (newObj.containsField("id")) {
                newObj.put("_id", newObj.get("id"));
                newObj.removeField("id");
            }
            insertData(schemaName, tableName, newObj, obj);
        }
    }


    public void delete(List<CanalEntry.Column> data, String schemaName, String tableName) {
        DBObject obj = DBConvertUtil.columnToJson(data);
        logger.debug("delete:{}", obj.toString());
        if (schemaName.equals("order")) {
            logger.info("not support delete:{}.{}:{}", schemaName, tableName, obj);
        } else {
            deleteData(schemaName, tableName, obj);
        }
    }


    public void update(List<CanalEntry.Column> data, String schemaName, String tableName) {
        DBObject obj = DBConvertUtil.columnToJson(data);
        logger.debug("update:{}", obj.toString());
        //订单库单独处理
        if (schemaName.equals("order")) {
            if (tableName.startsWith("order_base_info")) {
                tableName = "order_base_info";
            } else if (tableName.startsWith("order_detail_info")) {
                tableName = "order_detail_info";
            } else {
                logger.info("unknown data:{}.{}:{}", schemaName, tableName, obj);
            }
            updateData(schemaName, tableName, new BasicDBObject("orderId", obj.get("orderId")), obj);
        } else {
            if (obj.containsField("id")) {
                updateData(schemaName, tableName, new BasicDBObject("_id", obj.get("id")), obj);
            } else {
                logger.info("unknown data structure");
            }
        }
    }

    public void drop(String tableName) {
        List<String> enableList = properties.queryDropEnableTableList();
        if (properties.isDropEnable() || (enableList != null && enableList.size() > 0 && enableList.contains(tableName))) {
            logger.warn("drop table {} from naive", tableName);
            System.out.println("drop table " + tableName + " from naive");
            //防止多表一起drop
            String[] names = tableName.split(",|`|'");
            for (String name : names) {
                if (StringUtils.isNotBlank(name)) {
                    naiveMongoTemplate.getCollection(name.trim()).remove(new BasicDBObject());
                }
            }
        }
    }

    public void insertData(String schemaName, String tableName, DBObject naive, DBObject complete) {
        int i = 0;
        DBObject logObj = (DBObject) ObjectUtils.clone(complete);
        //保存原始数据
        try {
            String path = "/" + schemaName + "/" + tableName + "/" + CanalEntry.EventType.INSERT.getNumber();
            i++;
            naiveMongoTemplate.getCollection(tableName).insert(naive);
            i++;
            SpringUtil.doEvent(path, complete);
            i++;
        } catch (MongoClientException | MongoSocketException clientException) {
            //客户端连接异常抛出,阻塞同步,防止mongodb宕机
            throw clientException;
        } catch (Exception e) {
            logError(schemaName, tableName, 1, i, logObj, e);
        }
    }

    public void updateData(String schemaName, String tableName, DBObject query, DBObject obj) {
        String path = "/" + schemaName + "/" + tableName + "/" + CanalEntry.EventType.UPDATE.getNumber();
        int i = 0;
        DBObject newObj = (DBObject) ObjectUtils.clone(obj);
        DBObject logObj = (DBObject) ObjectUtils.clone(obj);
        //保存原始数据
        try {
            obj.removeField("id");
            i++;
            naiveMongoTemplate.getCollection(tableName).update(query, obj);
            i++;
            SpringUtil.doEvent(path, newObj);
            i++;
        } catch (MongoClientException | MongoSocketException clientException) {
            //客户端连接异常抛出,阻塞同步,防止mongodb宕机
            throw clientException;
        } catch (Exception e) {
            logError(schemaName, tableName, 2, i, logObj, e);
        }
    }


    public void deleteData(String schemaName, String tableName, DBObject obj) {
        int i = 0;
        String path = "/" + schemaName + "/" + tableName + "/" + CanalEntry.EventType.DELETE.getNumber();
        DBObject newObj = (DBObject) ObjectUtils.clone(obj);
        DBObject logObj = (DBObject) ObjectUtils.clone(obj);
        //保存原始数据
        try {
            i++;
            if (obj.containsField("id")) {
                naiveMongoTemplate.getCollection(tableName).remove(new BasicDBObject("_id", obj.get("id")));
            }
            i++;
            SpringUtil.doEvent(path, newObj);
        } catch (MongoClientException | MongoSocketException clientException) {
            //客户端连接异常抛出,阻塞同步,防止mongodb宕机
            throw clientException;
        } catch (Exception e) {
            logError(schemaName, tableName, 3, i, logObj, e);
        }
    }

    /**
     * 记录拼接表错误记录
     *
     * @param schemaName
     * @param tableName
     * @param event      1:INSERT;2:UPDATE;3:DELETE
     * @param step       0:预处理数据错误;1:保存原始数据错误;2:保存组合数据错误,
     * @param obj
     * @param e
     */
    private void logError(String schemaName, String tableName, int event, int step, DBObject obj, Exception e) {
        logger.error("error data:name[{},{}] , eventType : {} , data : [{}]", schemaName, tableName, event, obj);
        DBObject errObj = new BasicDBObject();
        errObj.put("schemaName", schemaName);
        errObj.put("tableName", tableName);
        errObj.put("event", event);
        errObj.put("data", obj);
        errObj.put("step", step);
        errObj.put("time", new Date());
        errObj.put("error", e.toString());
        completeMongoTemplate.getCollection(NameConst.C_ERROR_RECORD).insert(errObj);
    }

    @Async("myTaskAsyncPool")
    public Future<Integer> doAsyncTask(String tableName, List<EventData> dataList, String destination) {
        try {
            MDC.put("destination", destination);
            logger.info("thread: " + Thread.currentThread().getName() + " is doing job :" + tableName);
            for (EventData eventData : dataList) {
                SpringUtil.doEvent(eventData.getPath(), eventData.getDbObject());
            }
        } catch (Exception e) {
            logger.error("thread:" + Thread.currentThread().getName() + " get Exception", e);
            return new AsyncResult(0);
        }
        return new AsyncResult(1);
    }

}