package com.lion.lionwebsite.Service;

import com.lion.lionwebsite.Dao.normal.GalleryMapper;
import com.lion.lionwebsite.Domain.Gallery;
import com.lion.lionwebsite.Domain.GalleryTask;
import com.lion.lionwebsite.Message.*;

import com.lion.lionwebsite.Util.CustomUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Promise;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Service
@Data
@Slf4j
public class RemoteService {

    ChannelFuture channelFuture;

    Channel channel;

    String ip = "5.255.110.45";

    short port = 26321;

    GalleryMapper galleryMapper;

    PushService pushService;

    HashMap<Integer, Promise<AbstractMessage>> promiseHashMap;

    EventLoop eventLoopGroup;

    ExecutorService downloadThread;

    Thread monitor;

    AtomicInteger atomicInteger;

    public RemoteService(GalleryMapper galleryMapper, PushService pushService){
        this.galleryMapper = galleryMapper;
        this.pushService = pushService;
        atomicInteger = new AtomicInteger(0);
        eventLoopGroup = new DefaultEventLoop();
        downloadThread = Executors.newCachedThreadPool();
        promiseHashMap = new HashMap<>();

        if(!initChannel()){     //如果远程服务器连接失败，则开启本地监听
            monitor = new Thread(this::monitorFunc);
            monitor.start();
        }
    }

    public boolean initChannel(){
        try {
            channelFuture = new Bootstrap()
                    .channel(NioSocketChannel.class)
                    .group(new NioEventLoopGroup())
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel channel){
                            channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(100000000, 1, 4));
                            channel.pipeline().addLast(new MessageCodec());
                            channel.pipeline().addLast(new LoggingHandler());
                            channel.pipeline().addLast(new MyChannelInboundHandlerAdapter());
                        }
                    }).connect(new InetSocketAddress(ip, port)).sync();
            log.info("connect success");
            if(pushService != null)
                pushService.storageNodeOnline();

            channel = channelFuture.channel();
            channel.writeAndFlush(new IdentityMessage("lionwebsite"));
            return true;
        }catch (Exception e){
            e.printStackTrace();
            log.info("connect node failed, wait for node back online");
            return false;
        }
    }

    public boolean isDead(){
        return channelFuture.channel() == null || !channelFuture.channel().isActive();
    }

    public byte addGalleryToQueue(Gallery gallery, byte type){
        GalleryTask galleryTask = new GalleryTask();
        galleryTask.setGid(gallery.getGid());
        galleryTask.setType(type);

        DownloadPostMessage dpm = new DownloadPostMessage();
        dpm.messageId = atomicInteger.getAndIncrement();
        dpm.setGalleryTask(galleryTask);
        channel.writeAndFlush(dpm);

        DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
        promiseHashMap.put(dpm.messageId, promise);
        try {
            boolean result = promise.await(10, TimeUnit.SECONDS);
            if(result){
                ResponseMessage rsm = (ResponseMessage)promise.getNow();
                return rsm.getResult();
            }
            else return -1;
        }catch (InterruptedException e){
            e.printStackTrace();
            return -1;
        }
    }

    public byte deleteGallery(Gallery gallery){
        DeleteGalleryMessage dgm = new DeleteGalleryMessage();
        dgm.setGalleryName(gallery.getName());
        dgm.messageId = atomicInteger.getAndIncrement();

        channel.writeAndFlush(dgm);
        DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
        promiseHashMap.put(dgm.messageId, promise);
        try{
            boolean result = promise.await(10, TimeUnit.SECONDS);
            if(result){
                ResponseMessage rsm = (ResponseMessage) promise.getNow();
                return rsm.getResult();
            }else return -1;
        }catch (InterruptedException e){
            return -1;
        }
    }

    public byte cachePreview(String galleryName, short page, File file){
        File parentFile = file.getParentFile();
        if(!parentFile.isDirectory())
            if(parentFile.mkdirs())
                log.info("创建文件夹{}成功", parentFile.getAbsolutePath());
            else
                log.error("创建文件夹{}失败", parentFile.getAbsolutePath());

        GalleryRequestMessage grm = new GalleryRequestMessage();
        grm.setGalleryName(galleryName);
        grm.setPage(page);

        grm.setType(GalleryRequestMessage.PREVIEW);
        grm.messageId = atomicInteger.getAndIncrement();
        DefaultPromise<Object> downloadPromise = new DefaultPromise<>(eventLoopGroup);
        DefaultPromise<Object> readyPromise = new DefaultPromise<>(eventLoopGroup);

        downloadThread.submit(() -> {
            try {
                short port = CustomUtil._findIdlePort();
                ServerSocketChannel ssChannel = ServerSocketChannel.open();
                ssChannel.bind(new InetSocketAddress("0.0.0.0", port));
                grm.setPort(port);
                readyPromise.setSuccess("");
                SocketChannel socketChannel = ssChannel.accept();
                FileChannel fileChannel = FileChannel.open(file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                while (socketChannel.read(byteBuffer) != -1){
                    byteBuffer.flip();
                    fileChannel.write(byteBuffer);
                    byteBuffer.clear();
                }
                fileChannel.close();
                socketChannel.close();
                ssChannel.close();
                log.info("缓存预览图:" + galleryName + " " + file.getName());
                downloadPromise.setSuccess("");
            }catch (IOException e){
                e.printStackTrace();
            }
        });

        try{
            readyPromise.await();
            channel.writeAndFlush(grm);
            DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
            promiseHashMap.put(grm.messageId, promise);
            boolean result = promise.await(10, TimeUnit.SECONDS);
            if(result){
                ResponseMessage rsm = (ResponseMessage) promise.getNow();
                if(rsm.getResult() == 0){
                    downloadPromise.await(10, TimeUnit.SECONDS);
                    return (byte) (downloadPromise.isSuccess()?0:1);
                }else{
                    return rsm.getResult();
                }
            }else {
               return -1;
            }
        }catch (InterruptedException e){
            return -1;
        }
    }

    public String queryPageName(String name, int page){
        GalleryPageQueryMessage gpqm = new GalleryPageQueryMessage();
        gpqm.setName(name);
        gpqm.setPage(page);
        gpqm.messageId = atomicInteger.getAndIncrement();

        channel.writeAndFlush(gpqm);
        DefaultPromise<AbstractMessage> promise = new DefaultPromise<>(eventLoopGroup);
        promiseHashMap.put(gpqm.messageId, promise);
        try{
            boolean result = promise.await(10, TimeUnit.SECONDS);
            if(result){
                gpqm = (GalleryPageQueryMessage) promise.getNow();
                if(gpqm.getResult() == 0)
                    return gpqm.getPageName();
                else {
                    log.error("galleryPageQuery error:" + gpqm.getResult());
                    return null;
                }
            }else{
                return null;
            }
        }catch (InterruptedException e){
            return null;
        }
    }

    public void monitorFunc(){
        System.out.println("监听端口： " + (port + 1) + " 等待节点上线");
        try(ServerSocket socket = new ServerSocket(port + 1)) {
            Socket client;
            while(true){
                client = socket.accept();
                client.close();
                if(client.getInetAddress().getHostAddress().equals("5.255.110.45")){
                    System.out.println("尝试连接");
                    initChannel();
                    socket.close();
                    break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    class MyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg)  {
            //如果不是响应信息或者响应信息为失败，则打印
            if(!(msg instanceof ResponseMessage rm) || rm.getResult() != 0)
                if(! (msg instanceof MaintainMessage))
                    System.out.println(msg);

            //下载状态
            if(msg instanceof DownloadStatusMessage dsm){
                GalleryTask[] galleryTasks = dsm.getGalleryTasks();
                for (GalleryTask galleryTask : galleryTasks) {
                    Gallery gallery = galleryMapper.selectGalleryByGid(galleryTask.getGid());
                    gallery.setProceeding(galleryTask.getProceeding());

                    if(!gallery.getName().equals(galleryTask.getName()) && galleryTask.getName() != null)
                        gallery.setName(galleryTask.getName());

                    if(galleryTask.getStatus() == GalleryTask.COMPRESS_COMPLETE) {
                        gallery.setStatus("下载完成");
                        pushService.downloadComplete(gallery);
                    }
                    else if(galleryTask.getStatus() == GalleryTask.COMPRESSING)
                        gallery.setStatus("压缩中");
                    else if(galleryTask.getProceeding() != 0)
                        gallery.setStatus("下载中");


                    log.info(gallery.getName() + "下载进度:" + gallery.getProceeding() + "/" + gallery.getPages());
                    galleryMapper.updateGallery(gallery);
                }
            }
            else if(msg instanceof ResponseMessage rsm)
                promiseHashMap.get(rsm.messageId).setSuccess(rsm);
            else if(msg instanceof GalleryPageQueryMessage gpqm)
                promiseHashMap.get(gpqm.messageId).setSuccess(gpqm);
        }

        @Override
        public void channelUnregistered(ChannelHandlerContext ctx) {
            System.out.println(ctx.channel());
            System.out.println(channel);
            if(ctx.channel() != null && ctx.channel().remoteAddress().toString().equals(channel.remoteAddress().toString())){
                System.out.println("activate monitor thread, waiting for node back online");
                pushService.storageNodeOffline();
                monitor = new Thread(RemoteService.this::monitorFunc);
                monitor.start();
            }
        }
    }
}
