package org.example.petshopdesktop.api;

import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.DesktopNotificationService;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

public class ChatRealtimeClient implements WebSocket.Listener {
    private static final ChatRealtimeClient INSTANCE = new ChatRealtimeClient();

    private final HttpClient httpClient;
    private final StringBuilder frameBuffer = new StringBuilder();
    private final AtomicInteger subscriptionCounter = new AtomicInteger(1);
    private final Map<String, String> destinationBySubscription = new HashMap<>();
    private final Object lock = new Object();

    private WebSocket webSocket;
    private boolean connecting;
    private boolean connected;
    private boolean conversationsSubscribed;
    private Long selectedConversationId;
    private String conversationsSubscriptionId;
    private String conversationMessagesSubscriptionId;
    private Consumer<ConversationResponse> conversationListener;
    private Consumer<MessageResponse> messageListener;
    private Consumer<String> statusListener;
    private volatile String currentStatus = "Chat disconnected";

    private final Map<Long, ConversationResponse> globalConversations = new HashMap<>();
    private final List<Consumer<Boolean>> notificationListeners = new ArrayList<>();
    private boolean lastNotificationState = false;

    private ChatRealtimeClient() {
        this.httpClient = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
    }

    public static ChatRealtimeClient getInstance() {
        return INSTANCE;
    }

    public void addNotificationListener(Consumer<Boolean> listener) {
        synchronized (lock) {
            notificationListeners.add(listener);
            listener.accept(lastNotificationState);
        }
    }

    public void initializeState(List<ConversationResponse> conversations) {
        synchronized (lock) {
            globalConversations.clear();
            for (ConversationResponse conv : conversations) {
                globalConversations.put(conv.getId(), conv);
            }
        }
        updateNotificationState();
    }

    public void markConversationReplied(Long conversationId, Long senderId) {
        synchronized (lock) {
            ConversationResponse conv = globalConversations.get(conversationId);
            if (conv != null) {
                conv.setLastSenderId(senderId);
            }
        }
        updateNotificationState();
    }

    public boolean hasActionableChats() {
        synchronized (lock) {
            UserSession session = UserSession.getInstance();
            Long currentUserId = session.getUserId();
            for (ConversationResponse conv : globalConversations.values()) {
                if ("CLOSED".equals(conv.getStatus())) continue;
                
                // Needs pickup
                if (conv.getHumanRequestedAt() != null && conv.getStaffId() == null) {
                    return true;
                }
                
                // Needs reply (assigned to me and last sender was someone else - customer)
                if (currentUserId != null && currentUserId.equals(conv.getStaffId())) {
                    if (conv.getLastSenderId() != null && !conv.getLastSenderId().equals(currentUserId)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    private void updateNotificationState() {
        boolean currentState = hasActionableChats();
        List<Consumer<Boolean>> listeners;
        synchronized (lock) {
            if (currentState == lastNotificationState) return;
            lastNotificationState = currentState;
            listeners = new ArrayList<>(notificationListeners);
        }
        for (Consumer<Boolean> listener : listeners) {
            listener.accept(currentState);
        }
    }

    public void setConversationListener(Consumer<ConversationResponse> conversationListener) {
        this.conversationListener = conversationListener;
    }

    public void setMessageListener(Consumer<MessageResponse> messageListener) {
        this.messageListener = messageListener;
    }

    public void setStatusListener(Consumer<String> statusListener) {
        this.statusListener = statusListener;
        if (statusListener != null) {
            statusListener.accept(currentStatus);
        }
    }

    public void connect() {
        String token = UserSession.getInstance().getJwtToken();
        if (token == null || token.isBlank()) {
            publishStatus("Chat disconnected");
            return;
        }

        synchronized (lock) {
            if (connected || connecting) {
                return;
            }
            connecting = true;
        }

        String wsUrl = ApiConfig.getInstance().getBaseUrl()
                .replaceFirst("^http://", "ws://")
                .replaceFirst("^https://", "wss://") + "/ws/chat";

        publishStatus("Connecting chat...");

        httpClient.newWebSocketBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .buildAsync(URI.create(wsUrl), this)
                .thenAccept(socket -> {
                    synchronized (lock) {
                        webSocket = socket;
                    }
                    socket.sendText("CONNECT\naccept-version:1.2\nhost:localhost\nAuthorization:Bearer " + token + "\n\n\0", true);
                })
                .exceptionally(ex -> {
                    synchronized (lock) {
                        resetConnectionState();
                    }
                    publishStatus("Chat unavailable");
                    return null;
                });
    }

    public void disconnect() {
        WebSocket socket;
        synchronized (lock) {
            socket = webSocket;
            resetConnectionState();
            selectedConversationId = null;
            conversationsSubscribed = false;
        }
        if (socket != null) {
            socket.sendText("DISCONNECT\n\n\0", true);
            socket.sendClose(WebSocket.NORMAL_CLOSURE, "bye");
        }
        publishStatus("Chat disconnected");
    }

    public void subscribeToConversations() {
        synchronized (lock) {
            conversationsSubscribed = true;
        }
        connect();
        synchronized (lock) {
            if (connected && conversationsSubscriptionId == null) {
                conversationsSubscriptionId = subscribeLocked("/topic/chat/conversations");
            }
        }
    }

    public void subscribeToConversation(Long conversationId) {
        synchronized (lock) {
            selectedConversationId = conversationId;
        }
        connect();
        synchronized (lock) {
            if (connected) {
                applySelectedConversationSubscriptionLocked();
            }
        }
    }

    public boolean isConnected() {
        synchronized (lock) {
            return connected;
        }
    }

    public boolean sendMessage(Long conversationId, String content) {
        String token = UserSession.getInstance().getJwtToken();
        if (token == null || token.isBlank()) {
            publishStatus("Chat send failed");
            return false;
        }
        String body;
        try {
            body = ApiClient.getInstance().getObjectMapper().writeValueAsString(new MessageRequest(content));
        } catch (Exception e) {
            publishStatus("Chat send failed");
            return false;
        }

        synchronized (lock) {
            if (!connected || webSocket == null) {
                connect();
                return false;
            }
            webSocket.sendText(
                    "SEND\ndestination:/app/chat/conversations/" + conversationId + "/messages\nAuthorization:Bearer " + token + "\ncontent-type:application/json\ncontent-length:" + body.getBytes(StandardCharsets.UTF_8).length + "\n\n" + body + "\0",
                    true
            );
            return true;
        }
    }

    private String subscribeLocked(String destination) {
        String subscriptionId = "sub-" + subscriptionCounter.getAndIncrement();
        destinationBySubscription.put(subscriptionId, destination);
        webSocket.sendText("SUBSCRIBE\nid:" + subscriptionId + "\ndestination:" + destination + "\n\n\0", true);
        return subscriptionId;
    }

    private void unsubscribeLocked(String subscriptionId) {
        destinationBySubscription.remove(subscriptionId);
        if (webSocket != null) {
            webSocket.sendText("UNSUBSCRIBE\nid:" + subscriptionId + "\n\n\0", true);
        }
    }

    private void applySubscriptionsLocked() {
        if (webSocket == null || !connected) {
            return;
        }
        if (conversationsSubscribed && conversationsSubscriptionId == null) {
            conversationsSubscriptionId = subscribeLocked("/topic/chat/conversations");
        }
        applySelectedConversationSubscriptionLocked();
    }

    private void applySelectedConversationSubscriptionLocked() {
        if (webSocket == null || !connected) {
            return;
        }

        String destination = selectedConversationId == null ? null : "/topic/chat/conversations/" + selectedConversationId;
        if (destination == null) {
            if (conversationMessagesSubscriptionId != null) {
                unsubscribeLocked(conversationMessagesSubscriptionId);
                conversationMessagesSubscriptionId = null;
            }
            return;
        }

        if (conversationMessagesSubscriptionId != null) {
            String currentDestination = destinationBySubscription.get(conversationMessagesSubscriptionId);
            if (destination.equals(currentDestination)) {
                return;
            }
            unsubscribeLocked(conversationMessagesSubscriptionId);
        }

        conversationMessagesSubscriptionId = subscribeLocked(destination);
    }

    private void resetConnectionState() {
        webSocket = null;
        connecting = false;
        connected = false;
        destinationBySubscription.clear();
        conversationsSubscriptionId = null;
        conversationMessagesSubscriptionId = null;
        globalConversations.clear();
        updateNotificationState();
    }

    private void handleFrame(String frame) {
        String normalized = frame.replace("\r\n", "\n");
        int separator = normalized.indexOf("\n\n");
        String headerPart = separator >= 0 ? normalized.substring(0, separator) : normalized;
        String bodyPart = separator >= 0 ? normalized.substring(separator + 2) : "";
        String[] headerLines = headerPart.split("\n");
        if (headerLines.length == 0) {
            return;
        }

        String command = headerLines[0];
        Map<String, String> headers = new HashMap<>();
        for (int i = 1; i < headerLines.length; i++) {
            int idx = headerLines[i].indexOf(':');
            if (idx > 0) {
                headers.put(headerLines[i].substring(0, idx), headerLines[i].substring(idx + 1));
            }
        }

        if ("CONNECTED".equals(command)) {
            synchronized (lock) {
                connecting = false;
                connected = true;
                applySubscriptionsLocked();
            }
            publishStatus("Chat connected");
            return;
        }

        if ("MESSAGE".equals(command)) {
            String destination;
            synchronized (lock) {
                destination = destinationBySubscription.get(headers.get("subscription"));
            }
            try {
                if (destination != null && destination.startsWith("/topic/chat/conversations/")) {
                    MessageResponse message = ApiClient.getInstance().getObjectMapper().readValue(bodyPart, MessageResponse.class);
                    if (messageListener != null) {
                        messageListener.accept(message);
                    }

                    Long currentUserId = UserSession.getInstance().getUserId();
                    if (message.getSenderId() != null && !message.getSenderId().equals(currentUserId)) {
                        DesktopNotificationService.getInstance()
                                .notifyNewMessage(message.getSenderDisplayName(), message.getContent());
                    }

                    // Also update globalConversation last sender if this is the active conversation
                    synchronized (lock) {
                        ConversationResponse conv = globalConversations.get(message.getConversationId());
                        if (conv != null) {
                            conv.setLastMessage(message.getContent());
                            conv.setLastSenderId(message.getSenderId());
                        }
                    }
                    updateNotificationState();
                } else {
                    ConversationResponse conversation = ApiClient.getInstance().getObjectMapper().readValue(bodyPart, ConversationResponse.class);
                    synchronized (lock) {
                        globalConversations.put(conversation.getId(), conversation);
                    }
                    if (conversationListener != null) {
                        conversationListener.accept(conversation);
                    }
                    updateNotificationState();
                }
            } catch (Exception e) {
                publishStatus("Chat update failed");
            }
            return;
        }

        if ("ERROR".equals(command)) {
            publishStatus("Chat error");
        }
    }

    private void publishStatus(String status) {
        currentStatus = status;
        if (statusListener != null) {
            statusListener.accept(status);
        }
    }

    @Override
    public void onOpen(WebSocket webSocket) {
        webSocket.request(1);
    }

    @Override
    public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
        synchronized (lock) {
            frameBuffer.append(data);
            int delimiter;
            while ((delimiter = frameBuffer.indexOf("\0")) >= 0) {
                String frame = frameBuffer.substring(0, delimiter);
                frameBuffer.delete(0, delimiter + 1);
                handleFrame(frame);
            }
        }
        webSocket.request(1);
        return CompletableFuture.completedFuture(null);
    }

    @Override
    public CompletionStage<?> onBinary(WebSocket webSocket, ByteBuffer data, boolean last) {
        webSocket.request(1);
        return CompletableFuture.completedFuture(null);
    }

    @Override
    public CompletionStage<?> onClose(WebSocket webSocket, int statusCode, String reason) {
        synchronized (lock) {
            resetConnectionState();
        }
        publishStatus("Chat disconnected");
        return CompletableFuture.completedFuture(null);
    }

    @Override
    public void onError(WebSocket webSocket, Throwable error) {
        synchronized (lock) {
            resetConnectionState();
        }
        publishStatus("Chat unavailable");
    }
}
