package com.m2pool.lease.netty.handler;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.m2pool.lease.constant.GpuBrand;
import com.m2pool.lease.dto.v2.CoinAndAlgorithmDto;
import com.m2pool.lease.dto.v2.MachineConfigDto;
import com.m2pool.lease.entity.*;
import com.m2pool.lease.mapper.*;
import com.m2pool.lease.netty.message.*;
import com.m2pool.lease.service.LeaseMachineConfigService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.m2pool.lease.netty.message.MethodConstant.PING;

@Component
@ChannelHandler.Sharable
public class ServerChannelHandler extends SimpleChannelInboundHandler<ClientMessage<Object>> {

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private LeaseShopMapper leaseShopMapper;

    @Resource
    private LeaseMachineConfigService leaseMachineConfigService;

    @Resource
    private LeaseMachineMapper leaseMachineMapper;

    @Resource
    private LeaseGpuConfigMapper leaseGpuConfigMapper;

    @Resource
    private LeaseMiningSoftwareConfigMapper leaseMiningSoftwareConfigMapper;

    @Resource
    private LeaseMachineTempConfigMapper leaseMachineTempConfigMapper;

    @Resource
    private LeaseOrderItemMapper leaseOrderItemMapper;

    @Resource
    private LeaseOrderMiningMapper leaseOrderMiningMapper;

    /**
     * 接收到客户端的信息
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ClientMessage<Object>  msg) throws Exception {
        //处理
        if (MethodConstant.AUTH_MACHINE_CODE.equals(msg.getMethod())){
            System.out.println("Netty tcp 服务端收到客户端下载初始化GPU消息");
            handlerDownloadClientMessage(ctx,msg);
        }else if (MethodConstant.MINING_REP.equals(msg.getMethod())){
            System.out.println("Netty tcp 服务端收到客户端开始挖矿信息");
            handlerClientMiningMessage(ctx,msg);
        }else{
            System.out.println("未知消息方法");
        }
    }



    /**
     * 处理客户端发送主机gpu信息消息业务
     * @param msg
     */
    private void handlerDownloadClientMessage(ChannelHandlerContext ctx,ClientMessage<Object> msg){
        String id = msg.getId();
        if (checkMessage(ctx,id)){
            String[] split = id.split("::");
            Object params = msg.getParams();
            GpuAndSoftMessage gpuAndSoftMessage = JSONUtil.toBean(params.toString(), GpuAndSoftMessage.class);
            Map<String, GpuMessage> gpus = gpuAndSoftMessage.getGpus();
            List<String> miningsofts = gpuAndSoftMessage.getMiningsofts();
            List<String> modelList = GpuBrand.trimBrand(gpus.values().stream().map(GpuMessage::getModel).collect(Collectors.toList()));
            //挖矿软件公共配置
            List<CoinAndAlgorithmDto> supportAlgorithmAndCoin = leaseMiningSoftwareConfigMapper.selectSupportAlgorithm(miningsofts,modelList);
            // 新增主机相关信息
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {
                    try {
                        LeaseMachine leaseMachine = leaseMachineMapper.selectOne(new LambdaQueryWrapper<LeaseMachine>()
                                .eq(LeaseMachine::getHostMac, id).eq(LeaseMachine::getDel, false));
                        if (leaseMachine == null){
                            LeaseShop shop = getShop(split[0]);
                            leaseMachine = LeaseMachine.builder()
                                    .shopId(shop.getId())
                                    .hostMac(id)
                                    .type(true)
                                    .state(true)
                                    .saleNumbers(0).build();
                            List<CoinAndAlgorithmDto> needAddAlgo = supportAlgorithmAndCoin.stream()
                                    .filter(CoinAndAlgorithmDto::getIsAdd).collect(Collectors.toList());

                            if (!needAddAlgo.isEmpty()){
                                List<CoinAndAlgorithmDto> needAdd = new ArrayList<>();
                                for (String model : modelList) {
                                    for (CoinAndAlgorithmDto coinAndAlgorithmDto : needAddAlgo) {
                                        needAdd.add(CoinAndAlgorithmDto.builder()
                                                .isEffect(coinAndAlgorithmDto.getIsEffect())
                                                .coin(coinAndAlgorithmDto.getCoin())
                                                .algorithm(coinAndAlgorithmDto.getAlgorithm())
                                                .model(model)
                                                .build()
                                        );
                                    }
                                }
                                //GPU配置信息配置
                                leaseGpuConfigMapper.insertOrUpdateBatchByGpuInfo(needAdd);
                            }
                            leaseMachineMapper.insert(leaseMachine);
                            List<LeaseMachineConfig> leaseMachineConfigs = packageInsert(leaseMachine.getId(),gpus, supportAlgorithmAndCoin);
                            leaseMachineConfigService.saveBatch(leaseMachineConfigs);
                        }else {
                            leaseMachine.setOnlineStatus(true);
                            leaseMachineMapper.updateById(leaseMachine);
                            //检测对照表
                            List<LeaseMachineConfig> needUpdateInfo = packageInsert(leaseMachine.getId(),gpus, supportAlgorithmAndCoin);
                            //带检测矿机
                            List<MachineConfigDto> needCheckInfo = leaseMachineMapper.getEffectMachineAndTempMachineList(leaseMachine.getId());
                            Map<Boolean, List<MachineConfigDto>> collect = needCheckInfo.stream().collect(Collectors.groupingBy(MachineConfigDto::getIsTemp));
                            checkMachineAndInsertOrUpdate(leaseMachine.getId(),needUpdateInfo, collect);
                        }
                    } catch (Exception e) {
                        // 回滚事务
                        status.setRollbackOnly();
                        throw e;
                    }
                }
            });
        }
    }


    /**
     * 封装矿机子表信息
     * 矿机详情表新增数据
     * @return
     */
    public List<LeaseMachineConfig> packageInsert(Long machineId,Map<String, GpuMessage> gpus,List<CoinAndAlgorithmDto> supportAlgorithmAndCoin){
       List<LeaseMachineConfig> list = new ArrayList<>();
        for (GpuMessage value : gpus.values()) {
            for (CoinAndAlgorithmDto coinAndAlgorithmDto : supportAlgorithmAndCoin) {
                if (coinAndAlgorithmDto.getModel() != null && value.getModel().contains(coinAndAlgorithmDto.getModel())){
                    list.add(LeaseMachineConfig.builder()
                            .machineId(machineId)
                            .brand(value.getBrand())
                            .name(value.getModel())
                            .memory(value.getMem())
                            .coin(coinAndAlgorithmDto.getCoin())
                            .algorithm(coinAndAlgorithmDto.getAlgorithm())
                            .status(coinAndAlgorithmDto.getIsEffect())
                            .powerDissipation(coinAndAlgorithmDto.getPowerDissipation())
                            .hashrate(coinAndAlgorithmDto.getHashRate())
                            .monthIncome(coinAndAlgorithmDto.getMonthIncome())
                            .build());
                }

            }
        }
        return list;
    }

    /**
     * 检测主机上gpu是否修改 并且根据情况做出相应的增删改操作
     * @param machineId 检测的主机id
     * @param needUpdateInfo 检测对照组
     * @param needCheckInfo 检测组
     * @return
     */
    public void checkMachineAndInsertOrUpdate(Long machineId,List<LeaseMachineConfig> needUpdateInfo,Map<Boolean, List<MachineConfigDto>> needCheckInfo){
        //先检测该矿机是否存在合约
        LeaseOrderItem leaseOrderItem = leaseOrderItemMapper.selectOne(new LambdaQueryWrapper<LeaseOrderItem>()
                .eq(LeaseOrderItem::getProductMachineId, machineId)
                .eq(LeaseOrderItem::getStatus, 1)
        );
        if (leaseOrderItem != null){
            LocalDateTime effectTime = leaseOrderItem.getCreateTime().plusDays(leaseOrderItem.getLeaseTime());
            needUpdateInfo.forEach(item-> item.setEffectTime(effectTime));
            List<MachineConfigDto> realMachineList = needCheckInfo.get(false);
            List<MachineConfigDto> tempMachineList = needCheckInfo.get(true);
            boolean realCheck = compare(needUpdateInfo, realMachineList);
            //主表信息和本次客户端发送消息不相同 + 并且临时表没有信息
            if(!realCheck && tempMachineList.isEmpty()){
                //直接向临时表中加入数据
                leaseMachineTempConfigMapper.insertBatch(needUpdateInfo);
                return;
            }
            boolean tempCheck = compare(needUpdateInfo, tempMachineList);
            if (!realCheck && !tempCheck){
                //删除临时表数据 + 新增临时表数据
                leaseMachineTempConfigMapper.delete(new LambdaQueryWrapper<LeaseMachineTempConfig>()
                        .eq(LeaseMachineTempConfig::getMachineId, machineId));
                leaseMachineTempConfigMapper.insertBatch(needUpdateInfo);
                return;
            }
            //ABA
            if (realCheck && !tempCheck){
                //删除临时表数据
                leaseMachineTempConfigMapper.delete(new LambdaQueryWrapper<LeaseMachineTempConfig>()
                        .eq(LeaseMachineTempConfig::getMachineId, machineId));
            }
        }else{
            //删除主表信息 + 新增主表数据
          leaseMachineConfigService.remove(new LambdaUpdateWrapper<LeaseMachineConfig>()
                    .eq(LeaseMachineConfig::getMachineId, machineId));
          leaseMachineConfigService.saveBatch(needUpdateInfo);
        }
    }


    public static boolean compare(List<LeaseMachineConfig> needUpdateInfo,
                                             List<MachineConfigDto> needCheckInfo) {
        // 如果数量不相等直接返回false
        if (needUpdateInfo.size() != needCheckInfo.size()) {
            return false;
        }

        // 创建两个列表的副本用于比较
        List<LeaseMachineConfig> list1 = new ArrayList<>(needUpdateInfo);
        List<MachineConfigDto> list2 = new ArrayList<>(needCheckInfo);

        // 比较每个元素
        Iterator<LeaseMachineConfig> it1 = list1.iterator();
        while (it1.hasNext()) {
            LeaseMachineConfig config1 = it1.next();
            boolean foundMatch = false;

            Iterator<MachineConfigDto> it2 = list2.iterator();
            while (it2.hasNext()) {
                MachineConfigDto config2 = it2.next();

                // 比较指定字段
                if (Objects.equals(config1.getBrand(), config2.getBrand()) &&
                        Objects.equals(config1.getName(), config2.getName()) &&
                        Objects.equals(config1.getMemory(), config2.getMemory()) &&
                        Objects.equals(config1.getCoin(), config2.getCoin()) &&
                        Objects.equals(config1.getAlgorithm(), config2.getAlgorithm())) {

                    it2.remove();
                    foundMatch = true;
                    break;
                }
            }

            if (!foundMatch) {
                return false;
            }
            it1.remove();
        }

        return list1.isEmpty() && list2.isEmpty();
    }

    /**
     * 处理客户端发送的挖矿开始信息(暂时不做任何处理)
     * @param msg
     */
    private void handlerClientMiningMessage(ChannelHandlerContext ctx,ClientMessage<Object> msg){
        if (checkMessage(ctx,msg.getId())){
            String[] split = msg.getId().split("::");
            Object params = msg.getParams();
            ClientConfigurationMining clientConfigurationMining = JSONUtil.toBean(params.toString(), ClientConfigurationMining.class);
            //修改 当前邮箱账户 对应的该矿池的 一些基本信息  TODO 这里可以设置真实开始时间 和 状态改为1
            leaseOrderMiningMapper.updateOrderMining(split[0],clientConfigurationMining);
        }
    }

    /**
     * 根据身份码获取店铺信息
     * @param authCode
     * @return
     */
    public LeaseShop getShop(String authCode){
        return leaseShopMapper.selectOne(new LambdaQueryWrapper<LeaseShop>()
                .eq(LeaseShop::getUserEmail, authCode));
    }


    /**
     * 检查消息的合法性
     * @return
     */
    private boolean checkMessage(ChannelHandlerContext ctx,String id){
        System.out.println("客户端消息id"+ id);
        String[] split = id.split("::");
        if (split.length != 2){
            return false;
        }
        LeaseShop leaseShop = getShop(split[0]);
        Channel channel = ctx.channel();
        ChannelManager.idMap.put(channel,id);
        //不存在对应身份码的店铺 发送消息给客户端
        if (leaseShop == null){
            sendErrorResp(ctx,id);
            return false;
        }
        return true;
    }

    /**
     * 身份码校验失败
     * @param ctx
     * @param id
     */
    public void sendErrorResp(ChannelHandlerContext ctx,String id){
        ServerMessage<String> errorMsg = new ServerMessage<>();
        errorMsg.setId(id);
        errorMsg.setResult(false);
        ctx.channel().writeAndFlush(errorMsg).syncUninterruptibly();
    }


    /**
     * 活跃的、有效的通道
     * 第一次连接成功后进入的方法
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        String ip = getIPString(ctx);
        System.out.println("ip:"+ip+"连接客户端");
        ChannelManager.addChannel(ip, ctx.channel());
    }

    /**
     * 不活动的通道
     * 连接丢失后执行的方法（client端可据此实现断线重连）
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx)  {
        System.out.println("ip:"+getIPString(ctx)+"断开连接");
        String id = ChannelManager.idMap.get(ctx.channel());
        //修改矿机离线状态为 离线
        if (id != null){
            leaseMachineMapper.update(LeaseMachine.builder().onlineStatus(false).build(),
                    new LambdaUpdateWrapper<LeaseMachine>().eq(LeaseMachine::getHostMac,id));
        }
        ChannelManager.removeChannel(getIPString(ctx));
        ctx.close();
    }

    /**
     * 异常处理
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        //发生异常，关闭连接
        System.out.println("引擎 {} 的通道发生异常，即将断开连接"+ getRemoteAddress(ctx));
        ctx.close();//再次建议close
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ClientMessage bean = null;
        if (msg instanceof String) {
            bean = JSONUtil.toBean(msg.toString(), ClientMessage.class);
        }
        super.channelRead(ctx, bean);
    }
    /**
     * 心跳机制，超时处理
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            String ip = getIPString(ctx);
            String id = ChannelManager.getIdByChannel(ip);
            if (event.state() == IdleState.READER_IDLE) {
                System.out.println(id+"用户写超时,接受pong"+System.currentTimeMillis());
                //System.out.println(id+"用户读超时,断开连接");
                ChannelManager.removeChannel(ip);
                ctx.disconnect();//断开
            } else if (event.state() == IdleState.WRITER_IDLE ) {
                System.out.println(id+"用户写超时,发送ping"+System.currentTimeMillis());
                if (id != null){
                    ClientMessage ping = new ClientMessage();
                    ping.setMethod(PING);
                    ping.setId(id);
                    ctx.writeAndFlush(ping);
                }
            }
            //else if (event.state() == IdleState.ALL_IDLE) {
            //    ChannelManager.removeChannel(ip);
            //    ctx.disconnect();
            //}
        }
    }

    /**
     * 获取client对象：ip+port
     *
     * @param ctx
     * @return
     */
    public String getRemoteAddress(ChannelHandlerContext ctx) {
        String socketString = "";
        socketString = ctx.channel().remoteAddress().toString();
        return socketString;
    }

    /**
     * 获取client的ip
     *
     * @param ctx
     * @return
     */
    public String getIPString(ChannelHandlerContext ctx) {
        String ipString = "";
        String socketString = ctx.channel().remoteAddress().toString();
        int colonAt = socketString.indexOf(":");
        ipString = socketString.substring(1, colonAt);
        return ipString;
    }

}
