package com.example.quiz.data.remote;

import android.content.Context;
import android.util.Log;

import com.example.quiz.data.model.Course;
import com.example.quiz.data.model.dtos.NotifPayload;
import com.example.quiz.utils.SharedPrefsHelper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import javax.inject.Inject;
import javax.inject.Singleton;

import dagger.hilt.android.qualifiers.ApplicationContext;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import ua.naiksoftware.stomp.Stomp;
import ua.naiksoftware.stomp.StompClient;
import ua.naiksoftware.stomp.dto.StompHeader;

@Singleton
public class StompManager {

    private final TokenManager tokenManager;
    private final NotificationHelper notificationHelper;

    private StompClient stompClient;
    private boolean isConnected = false;

    private String url = "wss://api-alr.pmu.ml/ws";

    SharedPrefsHelper prefsHelper;

    // Map topic -> liste de listeners
    private final Map<String, List<Consumer<String>>> topicListeners = new HashMap<>();

    // Gestion des Disposables RxJava
    private final CompositeDisposable compositeDisposable = new CompositeDisposable();

    // Map pour stocker les subscriptions par topic
    private final Map<String, Disposable> topicSubscriptions = new HashMap<>();
    private String genererUrlWebSocket(String httpUrl) {
        if (httpUrl == null || httpUrl.isEmpty()) {
            return "wss://api-alr.pmu.ml/ws"; // Sécurité au cas où
        }

        String wsUrl = httpUrl;

        // 1. On remplace le protocole au début
        if (wsUrl.startsWith("https://")) {
            wsUrl = wsUrl.replace("https://", "wss://");
        } else if (wsUrl.startsWith("http://")) {
            wsUrl = wsUrl.replace("http://", "ws://");
        }

        // 2. On remplace la route finale "/api/" ou "/api" par "/ws"
        if (wsUrl.endsWith("/api/")) {
            // Enlève "/api/" (5 caractères) à la fin et ajoute "/ws"
            wsUrl = wsUrl.substring(0, wsUrl.length() - 5) + "/ws";
        } else if (wsUrl.endsWith("/api")) {
            // Enlève "/api" (4 caractères) à la fin et ajoute "/ws"
            wsUrl = wsUrl.substring(0, wsUrl.length() - 4) + "/ws";
        }

        return wsUrl;
    }

    @Inject
    public StompManager(TokenManager tokenManager,
                        NotificationHelper notificationHelper, Context context) {
        prefsHelper = SharedPrefsHelper.getInstance(context);
        String apiUrl = prefsHelper.get("MAIN_API_URL");
        if(apiUrl != null){
            url = genererUrlWebSocket(apiUrl);
        }
        this.tokenManager = tokenManager;
        this.notificationHelper = notificationHelper;
    }

    // -------------------- Connexion --------------------
    public synchronized void connect() {
        if (isConnected) return;

        String token = tokenManager.getToken();
        if (token == null) {
            Log.e("STOMP", "No token available");
            return;
        }

        // URL de connexion WebSocket


        try {
            // Créer le client STOMP
            stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, url);

            // Ajouter les headers d'authentification
            List<StompHeader> headers = new ArrayList<>();
            headers.add(new StompHeader("Authorization", "Bearer " + token));

            // Observer la connexion et stocker le Disposable
            Disposable lifecycleDisposable = stompClient.lifecycle()
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(lifecycleEvent -> {
                        switch (lifecycleEvent.getType()) {
                            case OPENED:
                                isConnected = true;
                                Log.d("STOMP", "Connected to WebSocket");
                                resubscribeAll();
                                break;

                            case CLOSED:
                                isConnected = false;
                                Log.d("STOMP", "Disconnected from WebSocket");
                                break;

                            case ERROR:
                                isConnected = false;
                                Log.e("STOMP", "Error: " + lifecycleEvent.getException());
                                reconnect();
                                break;
                        }
                    }, throwable -> {
                        Log.e("STOMP", "Lifecycle error", throwable);
                        isConnected = false;
                        reconnect();
                    });

            compositeDisposable.add(lifecycleDisposable);

            // Se connecter
            stompClient.connect(headers);

        } catch (Exception e) {
            Log.e("STOMP", "Connection error", e);
            reconnect();
        }
    }

    // -------------------- Souscription aux topics --------------------
    public synchronized void subscribe(String topic, Consumer<String> listener) {
        // Ajouter le listener
        topicListeners.computeIfAbsent(topic, k -> new ArrayList<>()).add(listener);

        // Si déjà connecté, souscrire immédiatement
        if (isConnected && stompClient != null) {
            subscribeToTopic(topic);
        } else {
            connect();
        }
    }

    private void subscribeToTopic(String topic) {
        if (stompClient == null) return;

        // Si déjà abonné à ce topic, ne pas souscrire à nouveau
        if (topicSubscriptions.containsKey(topic)) {
            Log.d("STOMP", "Already subscribed to " + topic);
            return;
        }

        // Formater le topic correctement
        String destination = topic.startsWith("/topic/") ? topic : "/topic/" + topic;

        Log.d("STOMP", "Subscribing to " + destination);

        Disposable subscription = stompClient.topic(destination)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        stompMessage -> {
                            String payload = stompMessage.getPayload();
                            Log.d("STOMP", "Message on " + destination + ": " + payload);

                            // Notifier les listeners
                            List<Consumer<String>> listeners = topicListeners.get(topic);
                            if (listeners != null) {
                                for (Consumer<String> listener : listeners) {
                                    try {
                                        listener.accept(payload);
                                    } catch (Exception e) {
                                        Log.e("STOMP", "Listener error", e);
                                    }
                                }
                            }
                            String notif = "";
                            Type type = new TypeToken<NotifPayload<Course>>(){}.getType();
                            NotifPayload<Course> notifCourse = new Gson().fromJson(payload, type);
                            Course updatedCourse = notifCourse.getPayload();
                            switch (notifCourse.getType()){
                                case COURSE_UPDATED:{
                                    if(updatedCourse.getStatut() == Course.Statut.OUVERT){
                                        notif = "Course "+updatedCourse.getNom()+" a été créée avec succès";
                                        break;
                                    }
                                }
                                case COURSE_CANCELLED:
                                    notif = "Course "+updatedCourse.getNom()+" annulée";
                                    break;
                                case COURSE_REPORTED:
                                    notif = "Course "+updatedCourse.getNom()+" reportée";
                                    break;
                                case COURSE_CLOSED_FOR_BETTING:
                                    notif = "Course "+updatedCourse.getNom()+" fermée aux paris!";
                                    break;
                                case RUNNER_DECLARED_NON_PARTANT:
                                    notif = "Non partants déclarés pour la course "+updatedCourse.getNom();
                                    break;
                            }
                            if(notif.isEmpty()) return;
                            notificationHelper.showNotification(topic, notif);
                        },
                        throwable -> {
                            Log.e("STOMP", "Error on " + destination, throwable);
                            // Nettoyer en cas d'erreur
                            topicSubscriptions.remove(topic);
                        }
                );

        topicSubscriptions.put(topic, subscription);
        compositeDisposable.add(subscription);
    }

    private void resubscribeAll() {
        if (stompClient == null) return;

        // Nettoyer les anciennes subscriptions
        for (Disposable disposable : topicSubscriptions.values()) {
            if (disposable != null && !disposable.isDisposed()) {
                disposable.dispose();
            }
        }
        topicSubscriptions.clear();

        // Resouscrire à tous les topics
        for (String topic : topicListeners.keySet()) {
            subscribeToTopic(topic);
        }
    }

    // -------------------- Envoi de messages --------------------
    public void sendMessage(String destination, Object payload) {
        if (!isConnected || stompClient == null) {
            Log.e("STOMP", "Not connected");
            return;
        }

        String dest = destination.startsWith("/app/") ? destination : "/app/" + destination;

        Disposable sendDisposable = stompClient.send(dest, payload.toString())
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        () -> Log.d("STOMP", "Message sent to " + dest),
                        throwable -> Log.e("STOMP", "Error sending to " + dest, throwable)
                );

        compositeDisposable.add(sendDisposable);
    }

    public void sendToCourses(Object payload) {
        sendMessage("/app/courses", payload);
    }

    // -------------------- Désabonnement --------------------
    public synchronized void unsubscribe(String topic, Consumer<String> listener) {
        if (topicListeners.containsKey(topic)) {
            topicListeners.get(topic).remove(listener);

            if (topicListeners.get(topic).isEmpty()) {
                topicListeners.remove(topic);

                // Si plus de listeners, annuler la souscription STOMP
                Disposable subscription = topicSubscriptions.remove(topic);
                if (subscription != null && !subscription.isDisposed()) {
                    subscription.dispose();
                    compositeDisposable.remove(subscription);
                }
            }
        }
    }

    // -------------------- Déconnexion --------------------
    public synchronized void disconnect() {
        // Nettoyer toutes les subscriptions RxJava
        compositeDisposable.clear();
        topicSubscriptions.clear();

        if (stompClient != null) {
            stompClient.disconnect();
            stompClient = null;
        }
        isConnected = false;
        topicListeners.clear();
    }

    private void reconnect() {
        disconnect();
        // Réessayer après délai
        new android.os.Handler().postDelayed(() -> {
            Log.d("STOMP", "Attempting to reconnect...");
            connect();
        }, 5000);
    }

    // À appeler dans le cycle de vie du fragment/activity
    public void onCleanup() {
        disconnect();
        compositeDisposable.dispose();
    }
}