package com.bgycc.smartcanteen.viewModel;

import android.text.TextUtils;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import com.bgycc.smartcanteen.entity.PayAck;
import com.bgycc.smartcanteen.entity.PayData;
import com.bgycc.smartcanteen.entity.PayRequest;
import com.bgycc.smartcanteen.entity.PayResponse;
import com.bgycc.smartcanteen.executor.SCTaskExecutor;
import com.bgycc.smartcanteen.repository.PayDataRepository;
import com.bgycc.smartcanteen.repository.PayResponseRepository;
import com.bgycc.smartcanteen.socket.SCWebSocketClient;
import com.bgycc.smartcanteen.socket.SCWebSocketListener;
import com.bgycc.smartcanteen.socket.SCWebSocketListenerAdapter;
import com.bgycc.smartcanteen.state.PayOnlineState;
import com.blankj.utilcode.util.LogUtils;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;

/**
 * 负责处理在线支付订单 <br/>
 * 将订单发送到后台,并接收后台返回的支付结果: <br/>
 * 1、"正在支付"状态 <br/>
 * 2、"支付成功",需检查是否为待处理订单,是否当前设备订单,符合后更新数据库状态 <br/>
 * 3、"支付失败",更新数据库状态 <br/>
 * 4、超时未处理订单将标记为需要"离线支付"订单 <br/>
 * 支付状态(空闲、发送订单信息、支付中、支付成功、支付失败)都会通过{@link PayOnlineState}发出通知 <br/><br/>
 */
public class PayOnlineViewModel extends ViewModel {
    private static final long TIMEOUT = 10 * 1000;
    // 在线支付延迟150ms执行,留出时间给扫码反馈
    private static final long REQUEST_DELAY = 150;
    private static final long DEFAULT_DELAY = 3 * 1000;
    private Gson gson;
    private String deviceSN;
    private PayDataRepository payDataRepository;
    private PayResponseRepository payResponseRepository;

    private MutableLiveData<PayOnlineState> payOnlineState = new MutableLiveData<>();

    public LiveData<PayOnlineState> getPayOnlineStateEvent() {
        return payOnlineState;
    }

    private PayRequest payRequest;
    private ScheduledFuture<?> timeoutFuture;

    public PayOnlineViewModel(PayDataRepository payDataRepository,
                              PayResponseRepository payResponseRepository,
                              Gson gson,
                              String deviceSN) {
        this.payDataRepository = payDataRepository;
        this.payResponseRepository = payResponseRepository;
        this.gson = gson;
        this.deviceSN = deviceSN;
    }

    public void initialize() {
        SCWebSocketClient.getInstance().addListener(listener);
    }

    public void exec(PayData payData) {
        payRequest = new PayRequest(deviceSN, payData);
        cancelTimeout();
        TimeoutRunnable timeoutRunnable = new TimeoutRunnable();
        timeoutFuture = SCTaskExecutor.getInstance().schedule(timeoutRunnable, TIMEOUT, TimeUnit.MILLISECONDS);
        RequestRunnable requestRunnable = new RequestRunnable();
        SCTaskExecutor.getInstance().schedule(requestRunnable, REQUEST_DELAY, TimeUnit.MILLISECONDS);
    }

    private void cancelTimeout() {
        if (timeoutFuture == null) return;
        timeoutFuture.cancel(true);
        timeoutFuture = null;
    }

    private SCWebSocketListener listener = new SCWebSocketListenerAdapter() {
        private static final String RESPONSE_MESSAGE = "message";
        private static final String RESPONSE_PAY_WAIT = "正在支付......";
        private static final String RESPONSE_PAY_RESULT = "PAY_RESULT";

        @Override
        public void onMessage(String action, JsonObject obj, String original) {
            String message = "";
            if (obj.has(RESPONSE_MESSAGE)) {
                message = obj.get(RESPONSE_MESSAGE).getAsString();
            }
            if (TextUtils.isEmpty(action) && message.equals(RESPONSE_PAY_WAIT)) {
                LogUtils.d(TAG, "正在支付: " + original);
                payOnlineState.postValue(new PayOnlineState(PayOnlineState.WAIT, message));
                return;
            }
            if (!action.equals(RESPONSE_PAY_RESULT)) return;
            // 在线支付结果需要匹配以下规则:
            // 1、action为"PAY_RESULT"
            if (payRequest == null || payRequest.getData().isEmpty()) {
                LogUtils.w(TAG, "后台返回在线支付结果,但没有待处理任务");
                payOnlineState.postValue(new PayOnlineState(PayOnlineState.IDLE));
                return;
            }
            LogUtils.d(TAG, "在线支付结果响应: " + original);
            ResponseRunnable runnable = new ResponseRunnable(original);
            SCTaskExecutor.getInstance().executeOnDiskIO(runnable);
        }
    };

    private class RequestRunnable implements Runnable {
        @Override
        public void run() {
            String requestStr = gson.toJson(payRequest);
            payOnlineState.postValue(new PayOnlineState(PayOnlineState.SEND, requestStr));
            SCWebSocketClient.getInstance().send(requestStr);
            LogUtils.d(TAG, "在线支付: " + payRequest.toString());
        }
    }

    private class ResponseRunnable implements Runnable {
        private String response;

        ResponseRunnable(String response) {
            this.response = response;
        }

        @Override
        public void run() {
            PayResponse payResponse = gson.fromJson(response, PayResponse.class);
            PayData latestData = payRequest.getData().get(0);
            // 需要检查:
            // 1、返回的结果设备id是否和当前设备一致
            // 2、返回的结果是否和在处理的订单一致
            if (!payResponse.matchDevice(deviceSN) || !matchPayRequest(latestData, payResponse)) {
                LogUtils.w(TAG, "订单号或设备不匹配\n" +
                        "结果: " + response +
                        "当前: " + payRequest.toString());
                return;
            }

            cancelTimeout();
            long lastInsertId = payResponseRepository.insertPayResponse(payResponse);
            if (lastInsertId == -1) {
                LogUtils.w(TAG, "在线支付结果插入数据库失败: " + response);
            }
            if (payResponse.paySuccess()) {
                latestData.paySuccess();
                payOnlineState.postValue(new PayOnlineState(PayOnlineState.SUCCESS, payResponse.getMessage(), response));
            } else {
                latestData.payFailed();
                payOnlineState.postValue(new PayOnlineState(PayOnlineState.FAILED, payResponse.getMessage(), response));
            }
            payDataRepository.updatePayData(latestData);
            // 通知服务器,已成功接收该通知
            PayAck ack = new PayAck(payResponse.getSerialNumber());
            SCWebSocketClient.getInstance().send(gson.toJson(ack));

            try {
                Thread.sleep(DEFAULT_DELAY);
            } catch (Exception ignored) {
            } finally {
                payOnlineState.postValue(new PayOnlineState(PayOnlineState.IDLE));
            }
        }

        private boolean matchPayRequest(PayData latestData, PayResponse response) {
            String latestPayCode = latestData.getPayCode();
            String responsePayCode = response.getPayCode();
            if (responsePayCode.equals(latestPayCode)) {
                return true;
            }
            findAndUpdatePayData(responsePayCode);
            return false;
        }

        private void findAndUpdatePayData(String target) {
            // 若服务器响应订单与当前在处理订单不一致
            // 则从数据库里获取对应订单并更新状态
            PayData matchPayData = payDataRepository.queryPayDataByPayCode(target);
            if (matchPayData.success()) {
                // 该订单可能支付了两次
                LogUtils.w(TAG, "订单号: " + matchPayData.toString() + " 支付了两次");
            } else {
                matchPayData.paySuccess();
                payDataRepository.updatePayData(matchPayData);
            }
        }
    }

    private class TimeoutRunnable implements Runnable {
        @Override
        public void run() {
            if (payRequest == null || payRequest.getData().isEmpty()) {
                LogUtils.w(TAG, "在线支付超时: 待处理任务为空");
                payOnlineState.postValue(new PayOnlineState(PayOnlineState.FAILED, "支付超时"));
                return;
            }
            // 虽然当前订单支付超时,但已标记"离线支付"状态,因此可通知用户已支付成功(等待扣费即可)
            payOnlineState.postValue(new PayOnlineState(PayOnlineState.SUCCESS));
            LogUtils.d(TAG, "订单支付超时,已标记离线支付: " + payRequest.toString());
            List<PayData> payDataList = payRequest.getData();
            for (PayData d : payDataList) {
                d.payOffline();
            }
            payDataRepository.updatePayData(payDataList);

            try {
                Thread.sleep(DEFAULT_DELAY);
            } catch (Exception ignored) {
            } finally {
                payOnlineState.postValue(new PayOnlineState(PayOnlineState.IDLE));
            }
        }
    }
}