package com.pwt.netty;

import com.pwt.StationService;
import com.pwt.netty.common.StringUtils;
import com.pwt.netty.common.constants.ExchangeWorkState;
import com.pwt.netty.common.vehicle2.Vehicle2Message;
import com.pwt.netty.common.vehicle2.client.HeartBeatRequest;
import com.pwt.netty.common.vehicle2.client.PowerSendRequest;
import com.pwt.netty.common.vehicle2.server.*;
import com.pwt.netty.handler.codec.vehicle2.Vehicle2ProtocolDecoder;
import com.pwt.netty.handler.codec.vehicle2.Vehicle2ProtocolEncoder;
import com.pwt.netty.message2.Assist;
import com.pwt.netty.util.AssertUtil;
import io.netty.bootstrap.ServerBootstrap;
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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.internal.SystemPropertyUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.InetSocketAddress;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static com.pwt.netty.MessageSender.MSG_ASSIST;
import static com.pwt.netty.ServerConstants.*;


/**
 * 车载换电控制器
 */
@Slf4j
public class Vehicle2Server {

    public static final Map<String, Channel> CHANNEL_MAP = new ConcurrentHashMap<>();
    public static final Map<String, String> CHANNEL_VIN_MAP = new ConcurrentHashMap<>();

    private static final int DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
        SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
    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 ServerBootstrap bootstrap;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;

    private Channel bossChannel;

    private final String host;
    private final int port;

    private final AtomicInteger currentState = new AtomicInteger(SERVER_STATUS_OFF);

    private StationService stationService;

    @Autowired
    public void setStationService(StationService stationService) {
        this.stationService = stationService;
    }

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

    public Vehicle2Server(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();
        initServerBootstrap();

        // bind
        ChannelFuture channelFuture = bootstrap.bind(new InetSocketAddress(host, port));
        channelFuture.syncUninterruptibly();
        bossChannel = channelFuture.channel();
    }

    protected void initServerBootstrap() {
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.DEBUG);

        bootstrap.group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 128)
            .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
            .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
            .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    pipeline.addLast(loggingHandler)
                        .addLast(new IdleStateHandler(3, 0, 0))
                        .addLast("frameDecoder", new Vehicle2ProtocolDecoder())
                        .addLast("protocolEncoder", new Vehicle2ProtocolEncoder())
                        .addLast("businessHandler", new SimpleChannelInboundHandler<Vehicle2Message>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, Vehicle2Message msg) throws Exception {
                                Channel channel = ctx.channel();
                                log.info("收到消息: {}", msg);

                                switch (msg.getType()) {
                                    case (byte) 0x01:
                                        // 度电数据
                                        if (stationService.getExchangeState() == ExchangeWorkState.IDLE.getValue()
                                            || stationService.getExchangeState() == ExchangeWorkState.MOVING.getValue()) {
                                            return;
                                        }

                                        if (stationService.isCurrentVin(msg.getVin())) {
                                            stationService.setVehiclePowerData((PowerSendRequest) msg);
                                            PowerSendResponse powerSendResponse = new PowerSendResponse();
                                            powerSendResponse.setVin(msg.getVin());
                                            powerSendResponse.setDateTime(LocalDateTime.now());
                                            ChannelFuture future1 = channel.writeAndFlush(powerSendResponse);
                                            future1.addListener(new ChannelFutureListener() {
                                                @Override
                                                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                                                    if (channelFuture.cause() == null && channelFuture.isSuccess()) {
                                                        log.info("power回复成功");
                                                    } else {
                                                        log.info("power回复失败");
                                                    }
                                                }
                                            });
                                        }
                                        break;

                                    case (byte) 0x03:
                                        // 心跳数据
                                        // 保存channel
                                        if (!CHANNEL_MAP.containsKey(msg.getVin())) {
                                            CHANNEL_MAP.put(msg.getVin(), channel);
                                            CHANNEL_VIN_MAP.put(channel.id().asLongText(), msg.getVin());
                                        } else {
                                            Channel channel1 = CHANNEL_MAP.get(msg.getVin());
                                            if (channel != channel1) {
                                                channel1.close();
                                                CHANNEL_MAP.put(msg.getVin(), channel);
                                            }
                                        }
                                        int state = stationService.getExchangeState();
                                        if (state < ExchangeWorkState.RFID.getValue() ||
                                            state == ExchangeWorkState.MOVING.getValue()) {
//                                            return;
                                        }

                                        if (!stationService.isCurrentVin(msg.getVin())) {
//                                            return;
                                        } else {
                                            stationService.setVehicleLockState((HeartBeatRequest) msg);
                                        }

                                        HeartBeatResponse heartBeatResponse = new HeartBeatResponse();
                                        heartBeatResponse.setVin(msg.getVin());
                                        heartBeatResponse.setDateTime(LocalDateTime.now());

                                        // 换电中返回2
                                        heartBeatResponse.setStationStatus(state == ExchangeWorkState.EXCHANGING.getValue() ? com.pwt.netty.common.constants.Constants.STATION_IDLE_FOR_CAR : (byte)1);
                                        ChannelFuture future = channel.writeAndFlush(heartBeatResponse);
                                        future.addListener(new ChannelFutureListener() {
                                            @Override
                                            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                                                if (channelFuture.cause() == null && channelFuture.isSuccess()) {
                                                    log.info("回复心跳成功");
                                                } else {
                                                    log.info("回复心跳失败");
                                                }
                                            }
                                        });
                                        break;
                                    default:
                                        break;
                                }
                            }

                            @Override
                            public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                if (evt instanceof IdleStateEvent) {
                                    if (((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
                                        ctx.close();
                                    }
                                }
                                super.userEventTriggered(ctx, evt);
                            }

                            @Override
                            public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                Channel channel = ctx.channel();
                                String channelId = channel.id().asLongText();
                                String vin = CHANNEL_VIN_MAP.get(channelId);
                                if (vin != null) {
                                    log.info("车辆连接断开:{}", vin);
                                    CHANNEL_MAP.remove(vin);
                                }
                                CHANNEL_VIN_MAP.remove(channelId);
                            }


                            @Override
                            public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                log.error("发生错误", cause);
                                ctx.channel().close();
                            }

                        });
                }
            });
    }

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

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

    /**
     * 停止服务
     */
    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();
                }
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            } catch (Exception ex) {
                // Ignore.
            }
        }
    }

    public static void unlock(String vin) {
        if(StringUtils.isEmpty(vin)) {
            return;
        }
        Channel channel = Vehicle2Server.CHANNEL_MAP.get(vin);
        if(channel == null || !channel.isActive()) {
            log.info("发送解锁失败，与车辆通讯中断：{}", vin);
            return;
        }

        UnlockRequest request = new UnlockRequest();
        request.setDateTime(LocalDateTime.now());
        request.setVin(vin);
        ChannelFuture channelFuture = channel.writeAndFlush(request);

        log.info("发送解锁指令, msg: {}", request);
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                if (channelFuture.cause() == null && channelFuture.isSuccess()) {
                    log.info("发送解锁指令成功");
                } else {
                    log.error("发送解锁指令失败", channelFuture.cause());
                }
            }
        });

    }

    public static void lock(String vin) {
        if(StringUtils.isEmpty(vin)) {
            return;
        }
        Channel channel = Vehicle2Server.CHANNEL_MAP.get(vin);
        if(channel == null || !channel.isActive()) {
            log.info("发送上锁失败，与车辆通讯中断：{}", vin);
            return;
        }

        LockRequest request = new LockRequest();
        request.setDateTime(LocalDateTime.now());
        request.setVin(vin);
        ChannelFuture channelFuture = channel.writeAndFlush(request);

        log.info("发送上锁指令, msg: {}", request);
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                if (channelFuture.cause() == null && channelFuture.isSuccess()) {
                    log.info("发送上锁指令成功");
                } else {
                    Assist assist = new Assist();
                    assist.setText("自动上锁异常");
                    assist.setMedia("http://localhost:8080/static/15.mp3");
                    MessageSender.send(MSG_ASSIST, assist);
                    log.error("发送上锁指令失败", channelFuture.cause());
                }
            }
        });

    }

    /**
     * 结算
     * @param vin
     */
    public static void settlement(String vin) {
        if(StringUtils.isEmpty(vin)) {
            return;
        }
        Channel channel = Vehicle2Server.CHANNEL_MAP.get(vin);
        if(channel == null || !channel.isActive()) {
            log.info("发送结算失败，与车辆通讯中断：{}", vin);
            return;
        }

        SettlementRequest request = new SettlementRequest();
        request.setDateTime(LocalDateTime.now());
        request.setVin(vin);
        ChannelFuture channelFuture = channel.writeAndFlush(request);

        log.info("发送结算指令, msg: {}", request);
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                if (channelFuture.cause() == null && channelFuture.isSuccess()) {
                    log.info("发送结算指令成功");
                } else {
                    log.error("发送结算指令失败", channelFuture.cause());
                }
            }
        });
    }
}
