package org.mercury_im.messenger.android.service;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.IBinder;

import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;

import org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.Notifications;
import org.mercury_im.messenger.android.ui.MainActivity;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.connection.state.ConnectionPoolState;
import org.mercury_im.messenger.core.connection.state.ConnectionState;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import io.reactivex.disposables.CompositeDisposable;

/**
 * Started, Bound Service, which is responsible keeping the application alive when the app is not open.
 */
public class MercuryForegroundService extends Service {

    private static final String APP = "org.mercury-im.messenger";
    private static final String SERVICE = APP + ".MercuryForegroundService";

    private static final String ACTION = SERVICE + ".ACTION";

    // ACTIONS
    public static final String ACTION_START = ACTION + ".START";
    public static final String ACTION_STOP = ACTION + ".STOP";

    private static final Logger LOGGER = Logger.getLogger(MercuryForegroundService.class.getName());

    @Inject
    MercuryConnectionManager connectionManager;

    CompositeDisposable disposable;

    @NonNull
    @Override
    public final IBinder onBind(Intent intent) {
        return new Binder(this);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        MercuryImApplication.getApplication().getAppComponent().inject(this);
        beginLifecycleOfPingManager();
        disposable = new CompositeDisposable();
        disposable.add(connectionManager.observeConnectionPool()
        .subscribe(state -> {
            Notification notification = buildForegroundNotification(getApplicationContext(), state);
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.notify(Notifications.FOREGROUND_SERVICE_ID, notification);
        }));
        LOGGER.log(Level.INFO, "onCreate");
    }

    /**
     * PingManager will ensure the XMPP connection is kept alive.
     */
    private void beginLifecycleOfPingManager() {
        ServerPingWithAlarmManager.onCreate(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        disposable.dispose();
        endLifecycleOfPingManager();
        LOGGER.log(Level.INFO, "onDestroy");
    }

    private void endLifecycleOfPingManager() {
        ServerPingWithAlarmManager.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent == null) {
            startAndDisplayForegroundNotification();
        } else {
            String action = intent.getAction();
            action = action == null ? "" : action;

            switch (action) {
                case ACTION_START:
                    startAndDisplayForegroundNotification();
                    break;
                case ACTION_STOP:
                    stopForeground(true);
                    stopSelf();
                    break;
                default:
                    break;
            }
        }

        return START_STICKY_COMPATIBILITY;
    }

    private void startAndDisplayForegroundNotification() {
        Notification notification = buildForegroundNotification(getApplicationContext(),
                connectionManager.observeConnectionPool().blockingFirst());
        startForeground(Notifications.FOREGROUND_SERVICE_ID, notification);
    }

    private static Notification buildForegroundNotification(Context context, ConnectionPoolState state) {
        Intent startMainActivityIntent = new Intent(context, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
                startMainActivityIntent, 0);

        Resources resources = context.getResources();
        String notificationText = notificationTextFrom(state, resources);

        return new NotificationCompat.Builder(context, Notifications.NOTIFICATION_CHANNEL__FOREGROUND_SERVICE)
                .setSmallIcon(R.drawable.ic_mercury_black_24dp)
                .setContentTitle(resources.getString(R.string.title_foreground_service))
                .setContentText(notificationText)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(notificationText))
                .setContentIntent(pendingIntent)
                .build();
    }

    private static String notificationTextFrom(ConnectionPoolState state, Resources resources) {
        List<UUID> connecting = new ArrayList<>();
        List<UUID> authenticated = new ArrayList<>();
        List<UUID> erred = new ArrayList<>();
        for (UUID id : state.getConnectionStates().keySet()) {
            ConnectionState connectionState = state.getConnectionStates().get(id);
            switch (connectionState.getConnectivity()) {
                case disconnected:
                    break;
                case connecting:
                case connected:
                    connecting.add(id);
                    break;
                case authenticated:
                    authenticated.add(id);
                    break;
                case disconnectedOnError:
                    erred.add(id);
                    break;
            }
        }

        StringBuilder sb = new StringBuilder();
        if (!authenticated.isEmpty()) {
            sb.append(resources.getQuantityString(R.plurals.foreground_service_accounts_connected,
                    authenticated.size(), authenticated.size()));
        }
        if (!connecting.isEmpty()) {
            if (!authenticated.isEmpty()) {
                sb.append("\n");
            }
            sb.append(resources.getQuantityString(R.plurals.foreground_service_accounts_connecting,
                    connecting.size(), connecting.size()));
        }
        if (!erred.isEmpty()) {
            if (!authenticated.isEmpty() || !connecting.isEmpty()) {
                sb.append("\n");
            }
            Iterator<UUID> iterator = erred.iterator();
            StringBuilder accounts = new StringBuilder();
            while (iterator.hasNext()) {
                UUID id = iterator.next();
                accounts.append(getAddress(state, id));
                if (iterator.hasNext()) accounts.append(", ");
            }
            sb.append(resources.getQuantityString(R.plurals.foreground_service_accounts_have_errors,
                    erred.size(), accounts));
        }
        return sb.toString();
    }

    private static String getAddress(ConnectionPoolState state, UUID uuid) {
        return state.getConnectionStates().get(uuid).getConnection().getAccount().getAddress();
    }

    public class Binder extends android.os.Binder {

        private final MercuryForegroundService service;

        public Binder(MercuryForegroundService service) {
            this.service = service;
        }

        public MercuryForegroundService getService() {
            return service;
        }
    }
}
