/*
 * 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.dao;

import com.github.dapeng.transaction.api.domain.*;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

import java.sql.*;
import java.util.List;

/**
 * Created by tangliu on 17/7/28.
 */
public class TransactionDaoImpl extends JdbcDaoSupport implements ITransactionDao {


    /**
     * 插入记录,返回id
     *
     * @param g
     * @return
     */
    @Override
    public Integer insert(TGlobalTransaction g) {

        final String strSql = "insert into global_transactions(status, curr_sequence, created_at, created_by, updated_by) values(?, ?, ?, ?, ?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        this.getJdbcTemplate().update(conn -> {
            int i = 0;
            PreparedStatement ps = conn.prepareStatement(strSql);
            ps = conn.prepareStatement(strSql, Statement.RETURN_GENERATED_KEYS);
            ps.setInt(++i, g.getStatus().getValue());
            ps.setInt(++i, g.getCurrSequence());
            ps.setTimestamp(++i, new Timestamp(g.getCreatedAt() == null ? new java.util.Date().getTime() : g.getCreatedAt().getTime()));
            ps.setInt(++i, g.getCreatedBy());
            ps.setInt(++i, g.getCreatedBy());
            return ps;
        }, keyHolder);

        return keyHolder.getKey().intValue();
    }

    /**
     * 插入子事务过程记录,返回id
     *
     * @param gp
     * @return
     */
    @Override
    public Integer insert(TGlobalTransactionProcess gp) {

        final String strSql = "insert into global_transaction_process(transaction_id,transaction_sequence,status,expected_status,service_name,version_name,method_name,rollback_method_name," +
                "request_json, response_json,redo_times,next_redo_time, created_by, updated_by,created_at) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        this.getJdbcTemplate().update(conn -> {
            int i = 0;
            PreparedStatement ps = conn.prepareStatement(strSql);
            ps = conn.prepareStatement(strSql, Statement.RETURN_GENERATED_KEYS);
            ps.setInt(++i, gp.getTransactionId());
            ps.setInt(++i, gp.getTransactionSequence());
            ps.setInt(++i, gp.getStatus().getValue());
            ps.setInt(++i, gp.getExpectedStatus().getValue());
            ps.setString(++i, gp.getServiceName());
            ps.setString(++i, gp.getVersionName());
            ps.setString(++i, gp.getMethodName());
            ps.setString(++i, gp.getRollbackMethodName());
            ps.setString(++i, gp.getRequestJson());
            ps.setString(++i, gp.getResponseJson());
            ps.setInt(++i, gp.getRedoTimes());
            ps.setTimestamp(++i, new Timestamp(gp.getNextRedoTime().getTime()));
            ps.setInt(++i, gp.getCreatedBy() == null ? 0 : gp.getCreatedBy());
            ps.setInt(++i, gp.getUpdatedBy() == null ? 0 : gp.getUpdatedBy());
            ps.setTimestamp(++i, new Timestamp(gp.getCreatedAt() == null ? new java.util.Date().getTime() : gp.getCreatedAt().getTime()));
            return ps;
        }, keyHolder);

        return keyHolder.getKey().intValue();
    }


    @Override
    public TGlobalTransaction getGlobalByIdForUpdate(Integer id) {

        return this.getJdbcTemplate().queryForObject("select * from global_transactions where id=? for update", new Object[]{id}, new int[]{Types.INTEGER}, new GlobalTransactionMapper());
    }

    @Override
    public TGlobalTransactionProcess getProcessByIdForUpdate(Integer id) {

        return this.getJdbcTemplate().queryForObject("select * from global_transaction_process where id=? for update", new Object[]{id}, new int[]{Types.INTEGER}, new GlobalTransactionProcessMapper());
    }

    /**
     * 查找所有的失败的或者未知的事务过程记录
     * 升序
     *
     * @param transactionId
     * @return
     */
    @Override
    public List<TGlobalTransactionProcess> findFailedProcess(Integer transactionId) {
        return this.getJdbcTemplate().query("select * from global_transaction_process where transaction_id=? and (status=3 or status=4) order by transaction_sequence asc", new Object[]{transactionId}, new GlobalTransactionProcessMapper());
    }

    /**
     * 查找所有的成功的或者未知的事务过程记录
     *
     * @param transactionId
     * @return
     */
    @Override
    public List<TGlobalTransactionProcess> findSucceedProcess(Integer transactionId) {
        return this.getJdbcTemplate().query("select * from global_transaction_process where transaction_id=? and (status=2 or status=4) order by transaction_sequence desc", new Object[]{transactionId}, new GlobalTransactionProcessMapper());
    }

    /**
     * 查询所有失败,或者部分回滚的全局事务记录
     *
     * @return
     */
    @Override
    public List<TGlobalTransaction> findFailedGlobals() {
        return this.getJdbcTemplate().query("select * from global_transactions where status=3 or status=5", new GlobalTransactionMapper());
    }


    /**
     * 查找所有状态为成功,但子过程中有失败的全局事务记录
     */
    @Override
    public List<TGlobalTransaction> findSuccessWithFailedProcessGlobals() {
        return this.getJdbcTemplate().query("select g.* from global_transactions g INNER JOIN global_transaction_process p ON g.id = p.transaction_id where g.status=2 and (p.status=3 or p.status=4) GROUP BY g.id", new GlobalTransactionMapper());
    }

    @Override
    public void updateProcessRollbackTime(Integer id, Integer redoTimes, java.util.Date nextRedoTime) {
        this.getJdbcTemplate().update("update global_transaction_process set redo_times = ?, next_redo_time=? where id=?",
                new Object[]{redoTimes, nextRedoTime, id}, new int[]{Types.INTEGER, Types.TIMESTAMP, Types.INTEGER});
    }


    /**
     * update global_transactions
     * set
     * status = ${status.getValue},
     * curr_sequence = ${currSequence},
     * updated_at = ${updatedAt}
     * where id = ${transactionId}
     **/
    @Override
    public void updateGlobalTransactionStatusAndCurrSeq(Integer status, Integer currSequence, Integer id) {
        this.getJdbcTemplate().update("update global_transactions set status=?, curr_sequence=? where id=?", new Object[]{status, currSequence, id}, new int[]{Types.INTEGER, Types.INTEGER, Types.INTEGER});
    }

    /**
     * update global_transaction_process
     * set
     * status = ${status.getValue},
     * response_json = ${responseJson},
     * updated_at = ${updatedAt}
     * where id = ${processId}
     *
     * @param id
     * @param status
     * @param response
     */
    @Override
    public void updateProcess(Integer id, Integer status, String response) {
        this.getJdbcTemplate().update("update global_transaction_process set status=?, response_json=? where id=?", new Object[]{status, response, id}, new int[]{Types.INTEGER, Types.VARCHAR, Types.INTEGER});
    }


    /**
     * 更新事务过程的期望状态
     *
     * @param id
     * @param expectedStatus
     */
    @Override
    public void updateProcessExpectedStatus(Integer id, Integer expectedStatus) {
        this.getJdbcTemplate().update("update global_transaction_process set expected_status=? where id=?", new Object[]{expectedStatus, id}, new int[]{Types.INTEGER, Types.INTEGER});
    }


    class GlobalTransactionMapper implements RowMapper<TGlobalTransaction> {

        @Override
        public TGlobalTransaction mapRow(ResultSet rs, int i) throws SQLException {
            TGlobalTransaction g = new TGlobalTransaction();
            g.setId(rs.getInt("id"));
            g.setCurrSequence(rs.getInt("curr_sequence"));
            g.setCreatedBy(rs.getInt("created_by"));
            g.setCreatedAt(rs.getDate("created_at"));
            g.setStatus(TGlobalTransactionsStatus.findByValue(rs.getInt("status")));
            g.setUpdatedAt(rs.getDate("updated_at"));
            g.setUpdatedBy(rs.getInt("updated_by"));
            return g;
        }
    }


    class GlobalTransactionProcessMapper implements RowMapper<TGlobalTransactionProcess> {

        @Override
        public TGlobalTransactionProcess mapRow(ResultSet rs, int i) throws SQLException {
            TGlobalTransactionProcess gp = new TGlobalTransactionProcess();
            gp.setId(rs.getInt("id"));
            gp.setUpdatedAt(rs.getTimestamp("updated_at"));
            gp.setCreatedBy(rs.getInt("created_by"));
            gp.setCreatedAt(rs.getTimestamp("created_at"));
            gp.setExpectedStatus(TGlobalTransactionProcessExpectedStatus.findByValue(rs.getInt("expected_status")));
            gp.setMethodName(rs.getString("method_name"));
            gp.setNextRedoTime(rs.getTimestamp("next_redo_time"));
            gp.setRedoTimes(rs.getInt("redo_times"));
            gp.setRequestJson(rs.getString("request_json"));
            gp.setResponseJson(rs.getString("response_json"));
            gp.setRollbackMethodName(rs.getString("rollback_method_name"));
            gp.setServiceName(rs.getString("service_name"));
            gp.setStatus(TGlobalTransactionProcessStatus.findByValue(rs.getInt("status")));
            gp.setTransactionId(rs.getInt("transaction_id"));
            gp.setTransactionSequence(rs.getInt("transaction_sequence"));
            gp.setUpdatedBy(rs.getInt("updated_by"));
            gp.setVersionName(rs.getString("version_name"));

            return gp;
        }
    }

}