package com.ttstd.controlled.signaling;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;

public class WebSocketClient {

    private static final String TAG = "WebSocketClient";

    // 应用层心跳：定期发送 PING，避免经过反向代理（nginx 等）空闲超时导致连接被断开。
    private static final long HEARTBEAT_INTERVAL_MS = 25_000;
    // OkHttp 协议层 ping：保持 TCP 通道活跃，与心跳互补。
    private static final long PING_INTERVAL_MS = 20_000;
    // 自动重连指数退避参数。
    private static final long RECONNECT_BASE_DELAY_MS = 2_000;
    private static final long RECONNECT_MAX_DELAY_MS = 30_000;
    private static final int RECONNECT_BACKOFF_STEPS = 5;

    private static final String PING_PAYLOAD = "{\"type\":\"PING\"}";

    private final String serverUrl;
    private final String deviceId;
    private final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    private OkHttpClient client;
    private volatile WebSocket webSocket;
    private SignalListener listener;

    private ScheduledExecutorService scheduler;
    private int reconnectAttempts = 0;
    private final AtomicBoolean manuallyClosed = new AtomicBoolean(false);

    public interface SignalListener {
        void onConnected();
        void onDisconnected();
        void onError(String error);
        void onMessage(SignalMessage message);
    }

    public WebSocketClient(String serverUrl, String deviceId) {
        this.serverUrl = serverUrl;
        this.deviceId = deviceId;
    }

    public void setListener(SignalListener listener) {
        this.listener = listener;
    }

    public void connect() {
        manuallyClosed.set(false);
        reconnectAttempts = 0;
        ensureScheduler();
        doConnect();
    }

    private void ensureScheduler() {
        if (scheduler == null || scheduler.isShutdown()) {
            scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
                Thread t = new Thread(r, "WebSocketClient-Scheduler");
                t.setDaemon(true);
                return t;
            });
        }
    }

    private void doConnect() {
        if (manuallyClosed.get()) {
            return;
        }
        Log.i(TAG, "Connecting to " + serverUrl);
        client = new OkHttpClient.Builder()
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(0, TimeUnit.SECONDS) // 不依赖读超时，由心跳/ping 保活
                .writeTimeout(15, TimeUnit.SECONDS)
                .pingInterval(PING_INTERVAL_MS, TimeUnit.MILLISECONDS)
                .build();

        Request request = new Request.Builder().url(serverUrl).build();
        client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket ws, Response response) {
                webSocket = ws;
                reconnectAttempts = 0;
                Log.i(TAG, "WebSocket connected");
                startHeartbeat();
                registerDevice();
                mainHandler.post(() -> {
                    if (listener != null) listener.onConnected();
                });
            }

            @Override
            public void onMessage(WebSocket ws, String text) {
                Log.d(TAG, "Received: " + text);
                try {
                    SignalMessage message = gson.fromJson(text, SignalMessage.class);
                    mainHandler.post(() -> {
                        if (listener != null) listener.onMessage(message);
                    });
                } catch (Exception e) {
                    Log.e(TAG, "Error parsing message", e);
                }
            }

            @Override
            public void onClosed(WebSocket ws, int code, String reason) {
                Log.i(TAG, "WebSocket closed: " + reason);
                webSocket = null;
                stopHeartbeat();
                mainHandler.post(() -> {
                    if (listener != null) listener.onDisconnected();
                });
                scheduleReconnect();
            }

            @Override
            public void onFailure(WebSocket ws, Throwable t, Response response) {
                Log.e(TAG, "WebSocket error: " + t.getMessage(), t);
                webSocket = null;
                stopHeartbeat();
                mainHandler.post(() -> {
                    if (listener != null) listener.onError(t.getMessage());
                });
                scheduleReconnect();
            }
        });
    }

    private void startHeartbeat() {
        if (scheduler == null || scheduler.isShutdown()) {
            return;
        }
        scheduler.scheduleAtFixedRate(() -> {
            WebSocket ws = webSocket;
            if (ws != null && !manuallyClosed.get()) {
                try {
                    ws.send(PING_PAYLOAD);
                } catch (Exception e) {
                    Log.w(TAG, "Heartbeat send failed", e);
                }
            }
        }, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS, TimeUnit.MILLISECONDS);
    }

    private void stopHeartbeat() {
        // 心跳任务随 scheduler 关闭或 webSocket 置空而停止，此处无需额外处理。
    }

    private void scheduleReconnect() {
        if (manuallyClosed.get()) {
            return;
        }
        if (scheduler == null || scheduler.isShutdown()) {
            return;
        }
        long delay = Math.min(
                RECONNECT_BASE_DELAY_MS * (1L << Math.min(reconnectAttempts, RECONNECT_BACKOFF_STEPS)),
                RECONNECT_MAX_DELAY_MS);
        reconnectAttempts++;
        Log.i(TAG, "Scheduling reconnect in " + delay + "ms (attempt " + reconnectAttempts + ")");
        scheduler.schedule(() -> {
            if (manuallyClosed.get()) {
                return;
            }
            Log.i(TAG, "Attempting reconnect...");
            doConnect();
        }, delay, TimeUnit.MILLISECONDS);
    }

    public void sendMessage(SignalMessage message) {
        WebSocket ws = webSocket;
        if (ws != null) {
            String json = gson.toJson(message);
            Log.d(TAG, "Sending: " + json);
            ws.send(json);
        }
    }

    public void disconnect() {
        manuallyClosed.set(true);
        WebSocket ws = webSocket;
        webSocket = null;
        stopHeartbeat();
        if (scheduler != null) {
            scheduler.shutdownNow();
            scheduler = null;
        }
        if (ws != null) {
            try {
                ws.close(1000, "Disconnecting");
            } catch (Exception ignored) {
            }
        }
        if (client != null) {
            client.dispatcher().executorService().shutdown();
            client = null;
        }
    }

    private void registerDevice() {
        SignalMessage registerMsg = new SignalMessage();
        registerMsg.setType("REGISTER");
        registerMsg.setFromDeviceId(deviceId);
        registerMsg.setDeviceType("CONTROLLED");
        sendMessage(registerMsg);
    }

    public boolean isConnected() {
        return webSocket != null;
    }
}
