package com.dspread.pos.common.websocket;

import android.util.Log;

import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class WebSocketManager {
    private static final String TAG = "WebSocketManager";
    private static WebSocketManager instance;

    private WebSocketClient webSocketClient;
    private final List<WebSocketListener> listeners = new ArrayList<>();
    private ScheduledExecutorService executorService;
    private String serverUrl;
    private final AtomicBoolean shouldReconnect = new AtomicBoolean(true);
    private static final int RECONNECT_DELAY_SECONDS = 5;
    private static final int MAX_RECONNECT_ATTEMPTS = 10;
    private int reconnectAttempts = 0;

    // Connection state tracking
    public enum ConnectionState {
        DISCONNECTED, CONNECTING, CONNECTED, ERROR
    }

    private ConnectionState connectionState = ConnectionState.DISCONNECTED;

    public interface WebSocketListener {
        void onMessageReceived(String message);
        void onConnectionEstablished();
        void onConnectionClosed();
        void onError(String error);
    }

    // Private constructor for singleton
    private WebSocketManager() {
        this.executorService = Executors.newSingleThreadScheduledExecutor();
    }

    public static synchronized WebSocketManager getInstance() {
        if (instance == null) {
            instance = new WebSocketManager();
        }
        return instance;
    }

    /**
     * Connect to WebSocket server
     */
    public synchronized void connect(String serverUrl) {
        this.serverUrl = serverUrl;

        if (isConnected()) {
            Log.d(TAG, "Already connected to WebSocket");
            notifyConnectionEstablished();
            return;
        }

        if (isConnecting()) {
            Log.d(TAG, "Already connecting to WebSocket");
            return;
        }

        internalConnect();
    }

    /**
     * Internal connection logic
     */
    private synchronized void internalConnect() {
        if (isConnected() || isConnecting()) {
            return;
        }

        Log.d(TAG, "Connecting to WebSocket: " + serverUrl);
        connectionState = ConnectionState.CONNECTING;
        reconnectAttempts++;

        try {
            URI uri = new URI(serverUrl);

            webSocketClient = new WebSocketClient(uri) {
                @Override
                public void onOpen(ServerHandshake handshakedata) {
                    Log.d(TAG, "WebSocket connection established");
                    connectionState = ConnectionState.CONNECTED;
                    reconnectAttempts = 0; // Reset on successful connection
                    shouldReconnect.set(true);
                    notifyConnectionEstablished();
                }

                @Override
                public void onMessage(String message) {
                    Log.d(TAG, "WebSocket message received: " + message);
                    notifyMessageReceived(message);
                }

                @Override
                public void onClose(int code, String reason, boolean remote) {
                    Log.d(TAG, "WebSocket connection closed. Code: " + code + ", Reason: " + reason);
                    connectionState = ConnectionState.DISCONNECTED;
                    notifyConnectionClosed();

                    if (shouldReconnect.get() && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
                        scheduleReconnect();
                    } else {
                        Log.w(TAG, "Max reconnect attempts reached or reconnection disabled");
                    }
                }

                @Override
                public void onError(Exception ex) {
                    Log.e(TAG, "WebSocket error: " + ex.getMessage());
                    connectionState = ConnectionState.ERROR;
                    notifyError(ex.getMessage());

                    if (shouldReconnect.get() && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
                        scheduleReconnect();
                    }
                }
            };

            // Set connection timeout
            webSocketClient.setConnectionLostTimeout(30);
            webSocketClient.connect();

        } catch (Exception e) {
            Log.e(TAG, "Error creating WebSocket client: " + e.getMessage());
            connectionState = ConnectionState.ERROR;
            notifyError(e.getMessage());
            scheduleReconnect();
        }
    }

    /**
     * Schedule reconnection attempt
     */
    private void scheduleReconnect() {
        if (shouldReconnect.get() && reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
            Log.d(TAG, "Scheduling reconnection in " + RECONNECT_DELAY_SECONDS + " seconds. Attempt: " + reconnectAttempts);

            executorService.schedule(() -> {
                if (shouldReconnect.get() && !isConnected() && !isConnecting()) {
                    Log.d(TAG, "Attempting reconnection...");
                    internalConnect();
                }
            }, RECONNECT_DELAY_SECONDS, TimeUnit.SECONDS);
        }
    }

    /**
     * Send message via WebSocket
     */
    public boolean sendMessage(String message) {
        if (webSocketClient != null && webSocketClient.isOpen()) {
            try {
                webSocketClient.send(message);
                Log.d(TAG, "Message sent: " + message);
                return true;
            } catch (Exception e) {
                Log.e(TAG, "Error sending message: " + e.getMessage());
                return false;
            }
        } else {
            Log.w(TAG, "Cannot send message - WebSocket not connected");
            return false;
        }
    }

    /**
     * Disconnect from WebSocket server
     */
    public synchronized void disconnect() {
        Log.d(TAG, "Disconnecting WebSocket");
        shouldReconnect.set(false);
        reconnectAttempts = 0;

        if (webSocketClient != null) {
            try {
                webSocketClient.close();
            } catch (Exception e) {
                Log.e(TAG, "Error closing WebSocket: " + e.getMessage());
            }
            webSocketClient = null;
        }
        connectionState = ConnectionState.DISCONNECTED;
    }

    /**
     * Check if WebSocket is connected
     */
    public boolean isConnected() {
        return webSocketClient != null && webSocketClient.isOpen() &&
                connectionState == ConnectionState.CONNECTED;
    }

    /**
     * Check if WebSocket is connecting
     */
    public boolean isConnecting() {
        return connectionState == ConnectionState.CONNECTING;
    }

    /**
     * Get current connection state
     */
    public ConnectionState getConnectionState() {
        return connectionState;
    }

    /**
     * Add WebSocket listener
     */
    public synchronized void addListener(WebSocketListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
            Log.d(TAG, "Listener added. Total listeners: " + listeners.size());
        }
    }

    /**
     * Remove WebSocket listener
     */
    public synchronized void removeListener(WebSocketListener listener) {
        listeners.remove(listener);
        Log.d(TAG, "Listener removed. Total listeners: " + listeners.size());
    }

    /**
     * Remove all listeners
     */
    public synchronized void removeAllListeners() {
        listeners.clear();
        Log.d(TAG, "All listeners removed");
    }

    /**
     * Notify all listeners about connection established
     */
    private synchronized void notifyConnectionEstablished() {
        Log.d(TAG, "Notifying " + listeners.size() + " listeners about connection");
        for (WebSocketListener listener : listeners) {
            try {
                listener.onConnectionEstablished();
            } catch (Exception e) {
                Log.e(TAG, "Error notifying listener: " + e.getMessage());
            }
        }
    }

    /**
     * Notify all listeners about message received
     */
    private synchronized void notifyMessageReceived(String message) {
        for (WebSocketListener listener : listeners) {
            try {
                listener.onMessageReceived(message);
            } catch (Exception e) {
                Log.e(TAG, "Error notifying listener: " + e.getMessage());
            }
        }
    }

    /**
     * Notify all listeners about connection closed
     */
    private synchronized void notifyConnectionClosed() {
        for (WebSocketListener listener : listeners) {
            try {
                listener.onConnectionClosed();
            } catch (Exception e) {
                Log.e(TAG, "Error notifying listener: " + e.getMessage());
            }
        }
    }

    /**
     * Notify all listeners about error
     */
    private synchronized void notifyError(String error) {
        for (WebSocketListener listener : listeners) {
            try {
                listener.onError(error);
            } catch (Exception e) {
                Log.e(TAG, "Error notifying listener: " + e.getMessage());
            }
        }
    }

    /**
     * Get current server URL
     */
    public String getServerUrl() {
        return serverUrl;
    }

    /**
     * Check if reconnection is enabled
     */
    public boolean isReconnectionEnabled() {
        return shouldReconnect.get();
    }

    /**
     * Enable/disable automatic reconnection
     */
    public void setReconnectionEnabled(boolean enabled) {
        shouldReconnect.set(enabled);
        if (!enabled) {
            reconnectAttempts = 0;
        }
    }

    /**
     * Get current reconnect attempt count
     */
    public int getReconnectAttempts() {
        return reconnectAttempts;
    }

    /**
     * Clean up resources
     */
    public void shutdown() {
        Log.d(TAG, "Shutting down WebSocketManager");
        disconnect();
        if (executorService != null && !executorService.isShutdown()) {
            executorService.shutdown();
        }
        removeAllListeners();
    }
}