/*******************************************************************************
 * Copyright (c) 2024-2025 R3944Realms. All rights reserved.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 *     http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.r3944realms.dg_lab.websocket;

import com.r3944realms.dg_lab.DgLab;
import com.r3944realms.dg_lab.api.manager.Status;
import com.r3944realms.dg_lab.utils.stringUtils.StringHandlerUtil;
import com.r3944realms.dg_lab.utils.stringUtils.UrlValidator;
import com.r3944realms.dg_lab.api.websocket.message.Message;
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.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.concurrent.CompletableFuture;

/**
 * 主要是处理netty逻辑以建立客户端
 */
public abstract class AbstractWebSocketClient {
    /**
     * 日志
     */
    protected static final Logger logger = LoggerFactory.getLogger(AbstractWebSocketClient.class);
    /**
     * 地址
     */
    protected volatile String Address;
    /**
     * 端口
     */
    protected volatile int Port;
    /**
     * 客户端启动类
     */
    private Bootstrap ClientBootstrap;
    /**
     * 客户端事件线程组
     */
    private EventLoopGroup ClientEventLoopGroup;
    /**
     * The Client channel.
     */
    protected Channel ClientChannel;

    private Thread WebSocketClientThread;
    /**
     * The Client status.
     */
    protected volatile Status ClientStatus = Status.WAITING_FOR_INIT;

    /**
     * Gets status.
     *
     * @return the status
     */
    public Status getStatus() {
        return this.ClientStatus;
    }

    /**
     * Sets status.
     *
     * @param status the status
     */
    public void setStatus(Status status) {
        this.ClientStatus = status;
    }

    /**
     * Instantiates a new Abstract web socket client.
     */
    protected AbstractWebSocketClient() {
        try {
            this.Address = InetAddress.getLocalHost().getHostAddress();
            this.Port = 9000;
        } catch (UnknownHostException e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * Instantiates a new Abstract web socket client.
     *
     * @param port the port
     */
    protected AbstractWebSocketClient(int port) {
        try {
            InetAddress localHost = InetAddress.getLocalHost();
            this.Address = localHost.getHostAddress();
            this.Port = UrlValidator.isValidPort(port) ? port : 9000;
        } catch (UnknownHostException e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * Instantiates a new Abstract web socket client.
     *
     * @param address the address
     * @param port    the port
     */
    protected AbstractWebSocketClient(String address, int port) {
        try {
            this.Address = UrlValidator.isValidAddress(address) ? address : InetAddress.getLocalHost().getHostAddress();
            this.Port = UrlValidator.isValidPort(port) ? port : 9000;
        } catch (UnknownHostException e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * Sets port.
     *
     * @param port the port
     */
    public void setPort(int port) {
        if (this.ClientStatus != Status.WAITING_FOR_INIT && this.ClientStatus != Status.STOPPED) {
            logger.error("Unable to change port to {}", port);
            return;
        }
        this.Port = UrlValidator.isValidPort(port) ? port : 9000;
    }

    /**
     * Sets address.
     *
     * @param address the address
     */
    public void setAddress(String address) {
        if (this.ClientStatus != Status.WAITING_FOR_INIT && this.ClientStatus != Status.STOPPED) {
            logger.error("Unable to change address to {}", address);
            return;
        }
        try {
            this.Address = UrlValidator.isValidAddress(address) ? address : InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sets address and port.
     *
     * @param address the address
     * @param port    the port
     */
    public void setAddressAndPort(String address, int port) {
        try {
            setAddress(address);
            setPort(port);
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }

    /**
     * Gets url.
     *
     * @return the url
     */
    public String getUrl() {
        return StringHandlerUtil.buildWebSocketURL(Address, Port, false);
    }

    /**
     * 提供实现此处添加对 Message处理的 Handler
     *
     * @param pipeline 管道
     */
    protected abstract void MessagePipeLineHandler(ChannelPipeline pipeline);

    /**
     * Init thread 0.
     */
    protected void initThread0() {}

    /**
     * Init thread.
     */
    protected final void initThread() {
        ClientEventLoopGroup = new NioEventLoopGroup();
        WebSocketClientThread = new Thread(() -> {
            ClientStatus = Status.STARTING;
            ClientBootstrap = new Bootstrap();
            ClientBootstrap.group(ClientEventLoopGroup);
            ClientBootstrap.channel(NioSocketChannel.class);
            ClientBootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(DgLab.LOGGING_HANDLER,
                            new HttpClientCodec(),
                            new HttpObjectAggregator(65536),
                            new WebSocketClientProtocolHandler(
                                    WebSocketClientHandshakerFactory.newHandshaker(
                                            URI.create(StringHandlerUtil.buildWebSocketURL(Address, Port, false)),
                                            WebSocketVersion.V13,
                                            null,
                                            false,
                                            new DefaultHttpHeaders()
                                    )
                            ),
                            new WebSocketFrameAggregator(65536));
                    MessagePipeLineHandler(pipeline);
                }
            });
            try {
                ChannelFuture channelFuture = ClientBootstrap.connect(Address, Port);
                ClientStatus = Status.RUNNING;
                started();
                ClientChannel = channelFuture.sync().channel();
                ClientChannel.closeFuture().sync();
            } catch (Exception e) {
                startingError();
                ClientStatus = Status.ERROR;
                logger.error(e.getMessage());
            } finally {
                if(ClientStatus == Status.ERROR || ClientStatus == Status.RUNNING) stop();
            }

        }, "WebSocketClient");
        initThread0();
    }

    /**
     * Start 0.
     */
    protected void start0() {}

    /**
     * Start.
     */
    public final void start() {
        starting();
        switch (ClientStatus) {
            case STARTING -> {
                startingError();
                logger.info("Client is already starting.");
            }
            case RUNNING -> {
                startingError();
                logger.info("Client is already running.");
            }
            case STOPPING -> {
                startingError();
                logger.info("Client is stopping");
            }
            case STOPPED, WAITING_FOR_INIT -> {
                initThread();
                WebSocketClientThread.start();
                start0();
            }
        }
    }

    /**
     * Stop 0 completable future.
     *
     * @return the completable future
     */
    protected CompletableFuture<Void> stop0() {
        return CompletableFuture.runAsync(() -> {});
    }

    /**
     * Starting.
     */
    protected abstract void starting();

    /**
     * Starting error.
     */
    protected abstract void startingError();

    /**
     * Started.
     */
    protected abstract void started();

    /**
     * Stopping.
     */
    protected abstract void stopping();

    /**
     * Stopping error.
     */
    protected abstract void stoppingError();

    /**
     * Stopped.
     */
    protected abstract void stopped();

    /**
     * Stop.
     */
    public final void stop() {
        stopping();
        switch (ClientStatus) {
            case WAITING_FOR_INIT -> {
                stoppingError();
                logger.warn("Not Init. (It shouldn't be happened)");
            }
            case STARTING -> {
                stoppingError();
                logger.info("Client is starting, please waiting.");
            }
            case RUNNING, ERROR -> {
                ClientStatus = Status.STOPPING;
                CompletableFuture<Void> CliChanClose = CompletableFuture.runAsync(() -> {
                    if (ClientChannel != null && ClientChannel.isOpen()) {
                        try {
                            ClientChannel.close().sync();
                        } catch (InterruptedException e) {
                            logger.error(e.getMessage());
                        } finally {
                            ClientChannel = null;
                        }
                    }
                });
                CompletableFuture<Void> CliEvenClose = CliChanClose.thenRunAsync(() -> {
                    if (ClientEventLoopGroup != null) {
                        try {
                            ClientEventLoopGroup.shutdownGracefully().sync();
                        } catch (InterruptedException e) {
                            logger.error(e.getMessage());
                        } finally {
                            ClientEventLoopGroup = null;
                        }
                    }
                });
                CliEvenClose.thenCombineAsync(stop0(), (a, b) -> {
                    ClientStatus = Status.STOPPED;
                    ClientBootstrap = null;
                    stopped();
                    return null;
                });
            }
            case STOPPING -> {
                stoppingError();
                logger.info("Client is already stopping");
            }
            case STOPPED -> {
                stoppingError();
                logger.info("Client has stopped");
            }
        }
    }

    /**
     * 向所连接的服务器发送消息
     *
     * @param message 消息
     */
    public abstract void send(Message message);
}
