package com.nokia.sms.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.Future;

import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;

import com.nokia.sms.config.BlkConfig;
import com.nokia.sms.entity.BindRespStatus;
import com.nokia.sms.entity.DelBlkBody;
import com.nokia.sms.entity.DelBlkRespStatus;
import com.nokia.sms.exception.ConnectFailedException;
import com.nokia.sms.exception.ParseException;
import com.nokia.sms.message.ClientMessageUtil;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class SocketClientService {

    private final ThreadPoolTaskScheduler socketScheduler;

    volatile Socket socket;
    volatile InputStream inputStream;
    volatile OutputStream outputStream;
    volatile boolean connected;
    // 0 - 4294967295 0xFFFFFFFF
    // 在进程间共享
    volatile long sequenceNumber;
    private static final long MAX_SEQUENCE_NUMBER = 4294967295L;
    volatile Future<?> heartbeatFuture;

    volatile String systemId = BlkConfig.getConfig("blkServer.systemId");
    volatile String password = BlkConfig.getConfig("blkServer.password");
    volatile String host = BlkConfig.getConfig("blkServer.ipAddress");
    volatile int port = Integer.parseInt(BlkConfig.getConfig("blkServer.port"));
    volatile int connectTimeout = Integer.parseInt(BlkConfig.getConfig("blkServer.connectTimeout"));
    volatile long heartbeatDelay = Long.parseLong(BlkConfig.getConfig("blkServer.heartbeatDelay"));

    public SocketClientService(ThreadPoolTaskScheduler socketScheduler) throws ConnectFailedException {
        this.socketScheduler = socketScheduler;
        connect();
    }

    /*
     * 完成删除黑名单业务
     */
    public boolean delBlk(String phoneNumber) {
        log.debug("准备删除号码 {} 的短信黑名单记录...", phoneNumber);
        Future<Boolean> future = socketScheduler.submit(() -> {
            if (!connected) {
                connect();
            }
            DelBlkBody delBlkBody = new DelBlkBody(phoneNumber);
            // 发送消息
            long sequenceNumberSend = getNextSequence();
            byte[] smitDelBlk = ClientMessageUtil.getSmitDelBlkMessage(delBlkBody, sequenceNumberSend);
            outputStream.write(smitDelBlk);
            outputStream.flush();
            log.debug("已发送解除黑名单消息 SMIT_DELBLK, 序列号: {} -- {}", sequenceNumberSend, Arrays.toString(smitDelBlk));
            // 接收回复消息
            byte[] resp = new byte[13];
            inputStream.read(resp);
            log.debug("收到服务器回复... {}", Arrays.toString(resp));
            DelBlkRespStatus status = ClientMessageUtil.parseSmitDelBlkRespMessage(resp);
            boolean b = (status == DelBlkRespStatus.SUCCESS);
            if (b) {
                // 删除成功, 序列号增加
                sequenceNumber = getNextSequence();
            }
            return b;
        });
        try {
            return future.get();
        } catch (Exception e) {
            log.error("删除黑名单出错...");
            e.printStackTrace();
            // 关闭连接，释放资源
            close();
            return false;
        }
    }

    /*
     * 连接到服务器
     */
    public void connect() throws ConnectFailedException {
        try {
            // 启动socket连接
            socket = new Socket();
            InetAddress address = InetAddress.getByName(host);
            socket.connect(new InetSocketAddress(address, port), connectTimeout);
            // 绑定输入输出
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            // 向客户端发送绑定请求
            byte[] smitBind = ClientMessageUtil.getSmitBindMessage(systemId, password);
            outputStream.write(smitBind);
            outputStream.flush();
            log.debug("已发送绑定请求消息 SMIT_BIND {}", Arrays.toString(smitBind));
            // 接收回复消息
            byte[] resp = new byte[13];
            int count = inputStream.read(resp);
            // 解析消息
            BindRespStatus status = ClientMessageUtil.parseSmitBindRespMessage(resp);
            log.debug("收到服务器回复...长度 {} 字节, 状态: {}--{}", count, status, Arrays.toString(resp));
            if (BindRespStatus.SUCCESS.equals(status)) {
                connected = true;
                log.info("已成功与服务端建立连接,开始心跳检测");
                // 开启心跳检测
                heartbeatFuture = socketScheduler.scheduleAtFixedRate(() -> {
                    heartbeat();
                }, new Date(System.currentTimeMillis() + heartbeatDelay), heartbeatDelay);
                return;
            } else {
                log.error("服务器连接失败，错误码： {}", status);
                // 连接失败释放资源
                close();
                throw new ConnectFailedException("连接失败，状态码 : " + status);
            }
        } catch (IOException | NoSuchAlgorithmException | ParseException e) {
            log.error("服务器连接失败");
            // 连接失败释放资源
            close();
            throw new ConnectFailedException(
                    String.format("连接服务器失败, 原因： %s - %s", e.getClass().getName(), e.getMessage()));
        }
    }

    /*
     * 心跳检测
     */
    public void heartbeat() {
        try {
            // 发送心跳消息
            long sequenceNumberSend = getNextSequence();
            byte[] smitActiveTest = ClientMessageUtil.getSmitActiveTestMessage(sequenceNumberSend);
            outputStream.write(smitActiveTest);
            outputStream.flush();
            log.debug("已发送心跳消息, 序列号: {}", sequenceNumberSend);
            // 读取心跳消息的返回
            byte[] buffer = new byte[48];
            inputStream.read(buffer);
            long sequenceNumberReceive = ClientMessageUtil.parseSmitActiveTestRespMessage(buffer);
            log.debug("接收到服务器返回的心跳消息, 序列号: {}", sequenceNumberReceive);
            if (sequenceNumberReceive == sequenceNumberSend) {
                sequenceNumber = getNextSequence();
            } else {
                log.error("心跳乱序，将关闭连接...发送 {} 接收 {} ", sequenceNumberSend, sequenceNumberReceive);
                close();
            }
        } catch (IOException | ParseException e) {
            log.error("心跳出错,将关闭连接... {}({})", e.getClass().getName(), e.getMessage());
            close();
        }
    }

    /*
     * 遇到问题关闭连接
     */
    public void close() {
        // 停止心跳检测
        heartbeatFuture.cancel(true);
        // 复位sequenceNumber
        sequenceNumber = 0;
        // 复位连接指示
        connected = false;
        // 释放资源
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            inputStream = null;
        }
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            outputStream = null;
        }
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            socket = null;
        }
    }

    private long getNextSequence() {
        if (sequenceNumber < MAX_SEQUENCE_NUMBER) {
            return sequenceNumber + 1L;
        } else {
            return 0L;
        }
    }
}
