/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.dapeng.transaction;

import com.github.dapeng.client.netty.JsonPost;
import com.github.dapeng.core.InvocationContext;
import com.github.dapeng.core.InvocationContextImpl;
import com.github.dapeng.core.SoaException;
import com.github.dapeng.core.helper.MasterHelper;
import com.github.dapeng.core.metadata.Service;
import com.github.dapeng.json.OptimizedMetadata;
import com.github.dapeng.metadata.MetadataClient;
import com.github.dapeng.transaction.api.domain.*;
import com.github.dapeng.transaction.api.service.GlobalTransactionProcessService;
import com.github.dapeng.transaction.dao.ITransactionDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import javax.xml.bind.JAXB;
import java.io.StringReader;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import static java.util.stream.Collectors.toList;

/**
 * Global Transaction Manager
 *
 * @author craneding
 * @date 16/4/12
 */
public class GlobalTransactionManager {

    Logger LOGGER = LoggerFactory.getLogger(GlobalTransactionManager.class);

    private AtomicBoolean working = new AtomicBoolean(false);

    @Autowired
    ITransactionDao transactionDao;

    @Autowired
    GlobalTransactionProcessService processService;

    @Transactional(value = "globalTransaction", rollbackFor = Exception.class)
    public void doJob() {

        if (!MasterHelper.isMaster("com.github.dapeng.transaction.api.service.GlobalTransactionService", "1.0.0")) {
            LOGGER.info("--- 定时事务管理器不是Master,跳过 ---");
            return;
        }
        if (working.get()) {
            return;
        }

        working.set(true);

        try {
            LOGGER.info("--- 定时事务管理器开始 ---");

            /**
             * 处理全局事务状态为失败或者部分回滚的记录,这些全局事务下的成功的子事务过程应该回滚
             */
            List<TGlobalTransaction> globalTransactionList = transactionDao.findFailedGlobals();

            LOGGER.info("需回滚全局事务数量:{} 编号集合:{}", globalTransactionList.size(), globalTransactionList.stream().map(gt -> gt.getId()).collect(toList()));

            for (TGlobalTransaction globalTransaction : globalTransactionList) {

                globalTransaction = transactionDao.getGlobalByIdForUpdate(globalTransaction.getId());
                if (globalTransaction.getStatus() != TGlobalTransactionsStatus.Fail && globalTransaction.getStatus() != TGlobalTransactionsStatus.PartiallyRollback) {
                    continue;
                }

                List<TGlobalTransactionProcess> transactionProcessList = transactionDao.findSucceedProcess(globalTransaction.getId());

                LOGGER.info("需回滚全局事务编号:{} 事务过程数量:{} 事务过程编号集合:{}", globalTransaction.getId(), transactionProcessList.size(), transactionProcessList.stream().map(gt -> gt.getId()).collect(toList()));

                if (transactionProcessList.isEmpty()) {
                    //如果事务过程为空,则说明该全局事务不需要再做处理,直接修改状态
                    transactionDao.updateGlobalTransactionStatusAndCurrSeq(TGlobalTransactionsStatus.HasRollback.getValue(), 0, globalTransaction.getId());
                    continue;
                }

                int i = 0;
                for (; i < transactionProcessList.size(); i++) {
                    TGlobalTransactionProcess process = transactionProcessList.get(i);

                    LOGGER.info("需回滚全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 事务过程原状态:{} 事务过程期望状态:{} 动作:开始处理",
                            globalTransaction.getId(), process.getId(), process.getTransactionSequence(),
                            process.getStatus().name(), TGlobalTransactionProcessExpectedStatus.HasRollback.name());

                    if (process.getNextRedoTime().after(new Date())) {
                        LOGGER.info("需回滚全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 未到下次处理时间,跳出", globalTransaction.getId(), process.getId(), process.getTransactionSequence());
                        break;
                    }

                    //更新process的期望状态为“已回滚”
                    if (process.getExpectedStatus() != TGlobalTransactionProcessExpectedStatus.HasRollback) {
                        try {
                            processService.updateExpectedStatus(process.getId(), TGlobalTransactionProcessExpectedStatus.HasRollback);
                        } catch (SoaException e) {
                            LOGGER.error(e.getMessage(), e);
                            break;
                        }
                    }

                    String responseJson;
                    //call roll back method
                    try {
                        responseJson = callServiceMethod(process, true);

                        //更新事务过程表为已回滚
                        transactionDao.updateProcess(process.getId(), TGlobalTransactionProcessStatus.HasRollback.getValue(), responseJson);

                        LOGGER.info("需回滚全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 事务过程原状态:{} 事务过程期望状态:{} 动作:已完成",
                                globalTransaction.getId(), process.getId(), process.getTransactionSequence(),
                                process.getStatus().name(), TGlobalTransactionProcessExpectedStatus.HasRollback.name());
                    } catch (Exception e) {
                        LOGGER.info("需回滚全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 事务过程原状态:{} 事务过程期望状态:{} 动作:异常({})",
                                globalTransaction.getId(), process.getId(), process.getTransactionSequence(),
                                process.getStatus().name(), TGlobalTransactionProcessExpectedStatus.HasRollback.name(), e.getMessage());

                        //更新事务过程表的重试次数和下次重试时间
                        try {
                            processService.updateRedoTimes(process.getId());
                        } catch (SoaException e1) {
                            LOGGER.error(e.getMessage(), e1);
                        }

                        LOGGER.error(e.getMessage(), e);
                        break;
                    }

                }

                if (i == 0) {
                    LOGGER.info("需回滚全局事务编号:{} 跳过", globalTransaction.getId());
                    continue;
                } else if (i == transactionProcessList.size()) {
                    //已回滚
                    transactionDao.updateGlobalTransactionStatusAndCurrSeq(TGlobalTransactionsStatus.HasRollback.getValue(), i > 0 ? transactionProcessList.get(i - 1).getTransactionSequence() : 0, globalTransaction.getId());
                    LOGGER.info("需回滚全局事务编号:{} 已完成", globalTransaction.getId());
                } else {
                    //部分回滚
                    transactionDao.updateGlobalTransactionStatusAndCurrSeq(TGlobalTransactionsStatus.PartiallyRollback.getValue(), i >= 0 ? transactionProcessList.get(i).getTransactionSequence() : 0, globalTransaction.getId());
                    LOGGER.info("需回滚全局事务编号:{} 部分完成", globalTransaction.getId());
                }
            }


            /**
             * 处理所有全局事务状态为成功,但对应子过程中存在失败状态的记录,这种情况下,应该对子过程顺序做向前处理
             */
            globalTransactionList = transactionDao.findSuccessWithFailedProcessGlobals();

            LOGGER.info("需向前全局事务数量:{} 编号集合:{}", globalTransactionList.size(), globalTransactionList.stream().map(gt -> gt.getId()).collect(toList()));

            for (TGlobalTransaction globalTransaction : globalTransactionList) {

                globalTransaction = transactionDao.getGlobalByIdForUpdate(globalTransaction.getId());
                if (globalTransaction.getStatus() != TGlobalTransactionsStatus.Success) {
                    continue;
                }

                List<TGlobalTransactionProcess> transactionProcessList = transactionDao.findFailedProcess(globalTransaction.getId());

                LOGGER.info("需向前全局事务编号:{} 事务过程数量:{} 事务过程编号集合:{}", globalTransaction.getId(), transactionProcessList.size(), transactionProcessList.stream().map(gt -> gt.getId()).collect(toList()));

                if (transactionProcessList.isEmpty()) {
                    continue;
                }

                int i = 0;
                for (; i < transactionProcessList.size(); i++) {
                    TGlobalTransactionProcess process = transactionProcessList.get(i);

                    LOGGER.info("需向前全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 事务过程原状态:{} 事务过程期望状态:{} 动作:开始处理",
                            globalTransaction.getId(), process.getId(), process.getTransactionSequence(),
                            process.getStatus().name(), TGlobalTransactionProcessExpectedStatus.Success.name());

                    if (process.getNextRedoTime().after(new Date())) {
                        LOGGER.info("需向前全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 未到下次处理时间,跳出", globalTransaction.getId(), process.getId(), process.getTransactionSequence());
                        break;
                    }

                    //更新process的期望状态为“成功”
                    if (process.getExpectedStatus() != TGlobalTransactionProcessExpectedStatus.Success) {
                        try {
                            processService.updateExpectedStatus(process.getId(), TGlobalTransactionProcessExpectedStatus.Success);
                        } catch (SoaException e) {
                            LOGGER.error(e.getMessage(), e);
                            break;
                        }
                    }

                    String responseJson;
                    //call method
                    try {
                        responseJson = callServiceMethod(process, false);

                        //更新事务过程表为成功
                        transactionDao.updateProcess(process.getId(), TGlobalTransactionProcessStatus.Success.getValue(), responseJson);

                        LOGGER.info("需向前全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 事务过程原状态:{} 事务过程期望状态:{} 动作:已完成",
                                globalTransaction.getId(), process.getId(), process.getTransactionSequence(),
                                process.getStatus().name(), TGlobalTransactionProcessExpectedStatus.Success.name());
                    } catch (Exception e) {
                        LOGGER.info("需向前全局事务编号:{} 事务过程编号:{} 事务过程序号:{} 事务过程原状态:{} 事务过程期望状态:{} 动作:异常({})",
                                globalTransaction.getId(), process.getId(), process.getTransactionSequence(),
                                process.getStatus().name(), TGlobalTransactionProcessExpectedStatus.Success.name(), e.getMessage());

                        //更新事务过程表的重试次数和下次重试时间
                        try {
                            processService.updateRedoTimes(process.getId());
                        } catch (SoaException e1) {
                            LOGGER.error(e.getMessage(), e1);
                        }

                        LOGGER.error(e.getMessage(), e);
                        break;
                    }

                }

                if (i == 0) {
                    LOGGER.info("需向前全局事务编号:{} 跳过", globalTransaction.getId());
                    continue;
                } else if (i == transactionProcessList.size()) {
                    //已经全部向前
//                    new GlobalTransactionUpdateAction(globalTransaction.getId(), i > 0 ? transactionProcessList.get(i - 1).getTransactionSequence() : 0, TGlobalTransactionsStatus.Success).execute();
                    LOGGER.info("需向前全局事务编号:{} 已完成", globalTransaction.getId());
                } else {
                    //部分向前,不更新状态
//                    new GlobalTransactionUpdateAction(globalTransaction.getId(), i >= 0 ? transactionProcessList.get(i).getTransactionSequence() : 0, TGlobalTransactionsStatus.Success).execute();
                    LOGGER.info("需向前全局事务编号:{} 部分完成", globalTransaction.getId());
                }
            }

            LOGGER.info("--- 定时事务管理器结束 ---");
        }catch (Exception e){
            LOGGER.error(e.getMessage(),e);
        }finally {
            working.set(false);
        }
    }


    private static String callServiceMethod(TGlobalTransactionProcess process, boolean rollbackOrForward) throws Exception {

        String responseJson;
        OptimizedMetadata.OptimizedService service = null;

        //获取服务的metadata
        String metadata = new MetadataClient(process.getServiceName(), process.getVersionName()).getServiceMetadata();
        if (metadata != null) {
            try (StringReader reader = new StringReader(metadata)) {
                service = new OptimizedMetadata.OptimizedService(JAXB.unmarshal(reader, Service.class));
            }
        }

        //获取服务的ip和端口
        JsonPost jsonPost = new JsonPost(process.getServiceName(), process.getVersionName(), process.getMethodName());

        InvocationContextImpl invocationContext = (InvocationContextImpl)InvocationContextImpl.Factory.currentInstance();
        invocationContext.serviceName(process.getServiceName());
        invocationContext.versionName(process.getVersionName());
        invocationContext.methodName(rollbackOrForward ? process.getRollbackMethodName() : process.getMethodName());
        invocationContext.callerMid("GlobalTransactionManager");
        invocationContext.transactionId(process.getTransactionId());
        invocationContext.transactionSequence(process.getTransactionSequence());

        if (rollbackOrForward) {
            responseJson = jsonPost.callServiceMethod("{}", service);
        } else {
            responseJson = jsonPost.callServiceMethod(process.getRequestJson(), service);
        }

        return responseJson;
    }
}