package com.pwt.netty;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.pwt.Globals;
import com.pwt.StationService;
import com.pwt.cloud.utils.CloudUtil;
import com.pwt.common.constant.LogConstants;
import com.pwt.common.utils.spring.SpringUtils;
import com.pwt.netty.common.charger.ChargerException;
import com.pwt.netty.common.charger.RequestMessage;
import com.pwt.netty.common.charger.ResponseMessage;
import com.pwt.netty.common.charger.client.ChargerChargeStatusData;
import com.pwt.netty.common.charger.client.ClientResponseData;
import com.pwt.netty.common.charger.server.StartStopCommand;
import com.pwt.netty.common.constants.ExchangeWorkState;
import com.pwt.netty.handler.ChargerPromiseHolder;
import com.pwt.netty.handler.ChargerServerHandler;
import com.pwt.netty.handler.codec.charger.ChargerFrameDecoder;
import com.pwt.netty.handler.codec.charger.ChargerProtocolDecoder;
import com.pwt.netty.handler.codec.charger.ChargerProtocolEncoder;
import com.pwt.netty.util.AssertUtil;
import com.pwt.netty.util.StringUtils;
import com.pwt.web.service.IPwtChargeRecordService;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ipfilter.IpFilterRule;
import io.netty.handler.ipfilter.IpFilterRuleType;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.SystemPropertyUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.net.InetSocketAddress;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import static com.pwt.Globals.chargeFinishParam;
import static com.pwt.Globals.emergencyChargerId;
import static com.pwt.netty.ServerConstants.*;

@Slf4j(topic = LogConstants.CHARGER_BIZ)
public class ChargerServer {

    // TODO 配置为max(最大仓位数, 2 * cpu核数)
    private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
            SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));

    private static final int MAX_RETRY_TIMES = 5;
    private static final int RETRY_SLEEP_MS = 2000;

    private static final String DEFAULT_HOST = "0.0.0.0";
    private static final int MIN_PORT = 1;
    private static final int MAX_PORT = 65535;

    private Map<String, Channel> channels;

    public static volatile DelayStartInfo delayStartInfo;
    private ServerBootstrap bootstrap;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

    private Channel bossChannel;

    private final String host;
    private final int port;
    private ChargerServerHandler handler;

    private final AtomicInteger currentState = new AtomicInteger(SERVER_STATUS_OFF);

    private final AtomicInteger failedTimes = new AtomicInteger(0);

    public static final ScheduledExecutorService DELAY_START_EXECUTOR = Executors.newScheduledThreadPool(1, new DefaultThreadFactory("charger_delay_start_worker", false));

    public static java.util.concurrent.Future<?> delayStartFuture;

    @Getter
    public static class DelayStartInfo {
        // 当这把枪停止时，启动startGun
        private final String triggerGunId;
        private final String delayStartGunId;

        public DelayStartInfo(String triggerGunId, String delayStartGunId) {
            this.triggerGunId = triggerGunId;
            this.delayStartGunId = delayStartGunId;
        }

        @Override
        public String toString() {
            return "{ triggerGunId = " + triggerGunId + " + delayStartGunId + " + delayStartGunId + "}";
        }
    }

    /**
     * 延时充电策略
     *
     * @param delayStartInfo
     */
    public synchronized void newDelayTask(final DelayStartInfo delayStartInfo) {
        if (delayStartInfo == null) {
            return;
        }
        log.info("new delay task. info: {}", delayStartInfo);
        delayStartFuture = DELAY_START_EXECUTOR.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    StationService stationService = SpringUtils.getBean(StationService.class);
                    String delayStartGunId = delayStartInfo.getDelayStartGunId();
                    if (stationService.getExchangeState() == ExchangeWorkState.MOVING.getValue() || stationService.getExchangeState() == ExchangeWorkState.EXCHANGING.getValue()) {
                        StationService.ChangeHolderInfo holderInfo = stationService.getHolderInfo();
                        if (holderInfo == null) {
                            log.info("延迟启动充电失败, 当前换电中，但是没有获取到holder信息");
                            return;
                        }
                        if (delayStartGunId.equals(ChargerServerHandler.HOLDER_AND_GUN_MAP.get(holderInfo.getGetNum())) ||
                                delayStartGunId.equals(ChargerServerHandler.HOLDER_AND_GUN_MAP.get(holderInfo.getPutNum()))) {
                            log.info("当前站控正在工作中，并且与延迟充电冲突, delayGunId={}, holder:{}", delayStartInfo.delayStartGunId, holderInfo);
                            return;
                        }
                    }
                    ResponseMessage responseMessage = startCharge(delayStartGunId.substring(0, delayStartGunId.length() - 1), delayStartInfo.getDelayStartGunId(), 0, false);
                    ClientResponseData responseData = ((ClientResponseData) responseMessage.getMessageData());
                    log.info("延迟启动充电成功, responseStatus:{}", responseData.getStatus());
                } catch (Exception e) {
                    log.info("延迟启动充电失败", e);
                } finally {
                    ChargerServer.delayStartInfo = null;
                }

            }
        }, 30, TimeUnit.SECONDS);
    }

    private IPwtChargeRecordService recordService;

    @Autowired
    public void setRecordService(IPwtChargeRecordService recordService) {
        this.recordService = recordService;
    }

    public IPwtChargeRecordService getRecordService() {
        return recordService;
    }

    public synchronized void cancelDelayTask() {
        if (delayStartFuture != null) {
            delayStartFuture.cancel(true);
            delayStartFuture = null;
        }
    }


    public ChargerServer(int port) {
        this(DEFAULT_HOST, port);
    }

    public ChargerServer(String host, int port) {
        AssertUtil.assertNotBlank(host, "host cannot be blank.");
        AssertUtil.isTrue(port >= MIN_PORT && port <= MAX_PORT, "port out of range.");
        this.host = host;
        this.port = port;
    }


    /**
     * 启动服务
     */
    public void start() throws Exception {
        if (!currentState.compareAndSet(SERVER_STATUS_OFF, SERVER_STATUS_STARTING)) {
            return;
        }
        bootstrap = new ServerBootstrap();
        bossGroup = createBossGroup();
        workerGroup = createWorkerGroup();
        final ChargerServerHandler chargerServerHandler = new ChargerServerHandler(this);
        this.handler = chargerServerHandler;
        this.channels = chargerServerHandler.getChannels();
        initServerBootstrap(chargerServerHandler);

        // bind
        ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress(host, port));
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.cause() != null) {
                    int failCount = failedTimes.incrementAndGet();
                    if (failCount > MAX_RETRY_TIMES) {
                        return;
                    }

                    if (log.isErrorEnabled()) {
                        log.error("[ChargerServer] server start failed (port={})=, failedTimes: {}", port, failCount, future.cause());
                    }
                    currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_OFF);
                    // 启动失败，尝试重启
                    try {
                        Thread.sleep(RETRY_SLEEP_MS);
                        start();
                    } catch (Throwable e) {
                        if (log.isErrorEnabled()) {
                            log.error("[ChargerServer]Failed to start server when retrying", e);
                        }
                    }
                } else {
                    if (log.isInfoEnabled()) {
                        log.info("[ChargerServer]server started success at host:{}, port: {}", host, port);
                    }
                    currentState.compareAndSet(SERVER_STATUS_STARTING, SERVER_STATUS_STARTED);
                }
            }
        });
        channelFuture.syncUninterruptibly();
        bossChannel = channelFuture.channel();
    }

    @Value("${commIp}")
    private String commIp;

    protected void initServerBootstrap(ChargerServerHandler chargerServerHandler) {
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.DEBUG);
        String tempIp = this.host;
        IpFilterRule chargeAcceptRule = new IpFilterRule() {
            @Override
            public boolean matches(InetSocketAddress inetSocketAddress) {
                String host = inetSocketAddress.getHostString();
                // TODO 从配置加载充电枪ip做白名单
                return StrUtil.equals(tempIp, host);
//                switch (host) {
//                    case "192.168.3.41":
//                    case commIp:
//                        return true;
//                    default:
//                        return false;
//                }
            }

            @Override
            public IpFilterRuleType ruleType() {
                return IpFilterRuleType.ACCEPT;
            }
        };

        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                // TODO netty 参数
                .option(ChannelOption.SO_BACKLOG, 128)
                .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)

                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        pipeline
                                .addLast(loggingHandler)
//                        .addLast("ipFilter", new RuleBasedIpFilter(chargeAcceptRule))
                                .addLast("frameDecoder", new ChargerFrameDecoder())
                                .addLast("protocolDecoder", new ChargerProtocolDecoder())
                                .addLast("protocolEncoder", new ChargerProtocolEncoder())
                                .addLast("chargerServerHandler", chargerServerHandler);

                    }
                })
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
                .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
                .childOption(ChannelOption.SO_TIMEOUT, 10)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.SO_RCVBUF, 32 * 1024);
    }

    protected EventLoopGroup createBossGroup() {
        return new NioEventLoopGroup(1, new DefaultThreadFactory("chargerBoss"));
    }

    protected EventLoopGroup createWorkerGroup() {
        return new NioEventLoopGroup(DEFAULT_EVENT_LOOP_THREADS, new DefaultThreadFactory("chargerWorker"));
    }

    /**
     * 停止服务
     */
    public void stop() throws Exception {

        while (currentState.get() == SERVER_STATUS_STARTING) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // Ignore.
            }
        }

        if (currentState.compareAndSet(SERVER_STATUS_STARTED, SERVER_STATUS_OFF)) {
            try {
                if (bossChannel != null) {
                    bossChannel.close();
                }

            } catch (Throwable e) {
                log.error("failed to close boss channel", e);
            }

            try {
                Collection<Channel> channels = getChannels();
                channels.forEach(Channel::close);
            } catch (Throwable e) {
                log.error("failed to close channels", e);
            }

            try {
                if (bossGroup != null) {
                    long timeout = 3000L;
                    long quietPeriod = 2000L;
                    Future<?> bossGroupShutdownFuture = bossGroup.shutdownGracefully(quietPeriod, timeout, TimeUnit.MILLISECONDS);
                    Future<?> workerGroupShutdownFuture = workerGroup.shutdownGracefully(quietPeriod, timeout, TimeUnit.MILLISECONDS);
                    bossGroupShutdownFuture.syncUninterruptibly();
                    workerGroupShutdownFuture.syncUninterruptibly();

                }
            } catch (Throwable e) {
                log.error("failed to shutdown event group", e);
            }

            try {
                if (channels != null) {
                    channels.clear();
                }
            } catch (Throwable e) {
                log.error("failed to clear channels", e);
            }

        }
    }

    public Collection<Channel> getChannels() {
        Collection<Channel> chs = new ArrayList<>(this.channels.size());
        // pick channels from NettyServerHandler ( needless to check connectivity )
        chs.addAll(this.channels.values());
        return chs;
    }

    private boolean isReady() {
        return currentState.get() == SERVER_STATUS_STARTED && handler != null && bossChannel != null && bossChannel.isActive();
    }

    public ResponseMessage stopCharge(String chargerId, String chargerGunId, int streamId) throws Exception {
        return startStopCharge(null, chargerId, chargerGunId, streamId, false, false);
    }

    public ResponseMessage startCharge(String chargerId, String chargerGunId, int streamId, boolean isEmergency) throws Exception {
        return startStopCharge(null, chargerId, chargerGunId, streamId, true, isEmergency);
    }

    public ResponseMessage startCharge(String vehicleCode, String chargerId, String chargerGunId, int streamId, boolean isEmergency) throws Exception {
        return startStopCharge(vehicleCode, chargerId, chargerGunId, streamId, true, isEmergency);
    }

    private ResponseMessage startStopCharge(String vehicleCode, String chargerId, String chargerGunId, int streamId, boolean isStart, boolean isEmergency) throws Exception {

        // todo 应急充电校验
        Channel channel = getChannel(chargerId, chargerGunId);

        if (isStart && !isEmergency) {
            ChargerChargeStatusData chargerChargeStatusData = ChargerServerHandler.CHARGER_CHARGE_STATUS_DATA_MAP.get(chargerGunId);
            if (chargerChargeStatusData.getChargingStatus() == 1 || chargerChargeStatusData.getSOC() == 100) {
                throw new ChargerException("该充电枪正在充电或电池已充满");
            }
        }

        return sendStarStopCommand(vehicleCode, chargerId, chargerGunId, streamId, isStart, isEmergency, channel);
    }

    private static ResponseMessage sendStarStopCommand(String vehicleCode, String chargerId, String chargerGunId, int streamId, boolean isStart, boolean isEmergency, Channel channel) throws InterruptedException, ChargerException {
        RequestMessage requestMessage = buildStartStopChargeRequestMessage(chargerId, chargerGunId, streamId, isStart, isEmergency);
        ChargerPromiseHolder.ChargerRequestKey key = new ChargerPromiseHolder.ChargerRequestKey(chargerGunId, requestMessage.getType(), requestMessage.getStreamId());
        try {

            channel.writeAndFlush(requestMessage)
                    .addListener(new ChannelFutureListener() {
                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (future.cause() != null) {
                                log.error("发送充电指令失败, msg{}", requestMessage, future.cause());
                            } else {
                                log.info("发送充电指令成功, msg{}", requestMessage);
                                if (isStart) {
                                    if (isEmergency) {
                                        chargeFinishParam.setDeviceCode(chargerGunId);
                                        chargeFinishParam.setVehicleCode(vehicleCode);
                                        ThreadUtil.execute(() -> CloudUtil.chargeStart(vehicleCode, chargerGunId));
                                    } else {
//                                        ThreadUtil.execute(() -> CloudUtil.powerStart(chargerGunId));
                                    }
                                }
                            }
                        }
                    });

            ChannelPromise promise = channel.newPromise();
            ChargerPromiseHolder.putPromise(key, promise);

            if (!promise.await(3000L, TimeUnit.MILLISECONDS)) {
                throw new ChargerException("发送充电指令超时");
            }

            AbstractMap.SimpleEntry<ChannelPromise, ResponseMessage> entry = ChargerPromiseHolder.getEntry(key);
            if (entry == null || entry.getValue() == null) {
                // Should not go through here.
                throw new ChargerException("发送充电指令失败, 未知错误");
            }
            return entry.getValue();
        } finally {
            ChargerPromiseHolder.remove(key);
        }
    }

    private Channel getChannel(String chargerId, String chargerGunId) throws ChargerException {
        if (StringUtils.isEmpty(chargerId) && StringUtils.isEmpty(chargerGunId)) {
            throw new IllegalArgumentException("chargerId and chargerGunId can not be empty");
        }

        if ((chargerGunId.length() - 1 != chargerId.length()) || !chargerGunId.startsWith(chargerId)) {

            throw new IllegalArgumentException("charger's or gun's id  is not valid");
        }
        if (!(emergencyChargerId + "3").equals(chargerGunId) && !(emergencyChargerId + "4").equals(chargerGunId)) {
            Integer holderId = ChargerServerHandler.GUN_AND_HOLDER_MAP.get(chargerGunId);
            if (holderId == null) {
                throw new ChargerException("illegality chargerGunId");
            }
        }

//        if (Globals.plcStateInfo == null || !Globals.plcStateInfo.getBatteryExists()[holderId - 1]) {
//            throw new ChargerException("无法从机器人获取电池状态或电池不存在");
//        }

        if (!isReady()) {
            throw new ChargerException("充电机服务未就绪");
        }

        Channel channel = this.handler.getChannel(chargerGunId);
        if (channel == null || !channel.isActive()) {
            throw new ChargerException("该充电机未连接");
        }
        return channel;
    }

    private static RequestMessage buildStartStopChargeRequestMessage(String chargerId, String chargerGunId, int streamId, boolean isStart, boolean isEmergency) {
        RequestMessage requestMessage = new RequestMessage();
        requestMessage.setType(0x50);
        requestMessage.setChargerAddress(chargerId);
        requestMessage.setGunNo(chargerGunId.charAt(chargerGunId.length() - 1));
        if (streamId > 0) {
            requestMessage.setStreamId(streamId);
        }
        StartStopCommand command = new StartStopCommand();
        command.setCommandType(isStart ? 1 : 0);
        command.setStartCondition((byte) 0);
        command.setChargeMode(2);
        command.setChargeData(0);
        command.setChargeCurrent((byte) 0);
        command.setChargeType(isEmergency ? 2 : 1);
        requestMessage.setMessageData(command);
        requestMessage.setLength(command.getProtocolLength());
        return requestMessage;
    }
}
