package com.eaze.locationbackgroundtimer;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;

import com.eaze.locationbackgroundtimer.connectivity.ConnectivityChangeReceiver;
import com.eaze.locationbackgroundtimer.track.PostLocationResponse;
import com.eaze.locationbackgroundtimer.track.PostLocationServiceManager;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

import static com.eaze.locationbackgroundtimer.LocationTimerModule.EVENT_LOCATION_RESPONSE;
import static com.eaze.locationbackgroundtimer.LocationTimerModule.EVENT_LOCATION_TIMEOUT;
import static com.eaze.locationbackgroundtimer.LocationTimerModule.EXTRA_RESPONSE_DELIVERY_TYPE;
import static com.eaze.locationbackgroundtimer.LocationTimerModule.EXTRA_RESPONSE_DRIVERMODE;
import static com.eaze.locationbackgroundtimer.LocationTimerModule.EXTRA_RESPONSE_EXCEPTION;
import static com.eaze.locationbackgroundtimer.LocationTimerModule.EXTRA_RESPONSE_STATUS;
import static com.eaze.locationbackgroundtimer.LocationTimerModule.EXTRA_RESPONSE_URL;
import static com.eaze.locationbackgroundtimer.LocationTimerModule.EXTRA_TIMEOUT;

public class LocationTimerService extends Service implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener, LocationListener {

    private static final String TAG = LocationTimerService.class.getSimpleName();
    private int mInterval;
    private String mAuthToken;
    private boolean mIsTimingOut = false;
    private ConnectivityChangeReceiver mConnectivityChangeReceiver;
    private static final int NOTIFICATION_ID = 101;

    /**
     * Provides the entry point to Google Play services.
     */
    private GoogleApiClient mGoogleApiClient;

    @Override
    public void onCreate() {
        super.onCreate();
        buildGoogleApiClient();
        setupConnectivityChangeListener();
    }

    private void setupConnectivityChangeListener() {
        // Register a receiver to get connectivity change events,
        // and resume the connection to the Google API client when connectivity is restored.
        mConnectivityChangeReceiver = new ConnectivityChangeReceiver(
                new ConnectivityChangeReceiver.ConnectivityResumedCallback() {
                    @Override
                    public void onConnectivityResumed() {
                        if (!mGoogleApiClient.isConnected()) {
                            Log.d(TAG, "Reconnecting Google API client");
                            mGoogleApiClient.connect();
                        } else {
                            Log.d(TAG, "Google API client still connected, taking no action");
                        }
                    }
                });
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        intentFilter.addAction("android.net.wifi.WIFI_STATE_CHANGED");
        this.registerReceiver(mConnectivityChangeReceiver, intentFilter);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mInterval = intent.getIntExtra(LocationTimerModule.TIMER_INTERVAL_KEY,
                LocationTimerModule.TIMER_INTERVAL_DEFAULT_VALUE);
        mAuthToken = intent.getStringExtra(LocationTimerModule.AUTH_TOKEN_KEY);
        Log.d(TAG, "Interval is " + mInterval);
        Log.d(TAG, "Token is " + mAuthToken);
        mGoogleApiClient.connect();
        makeForegroundServiceNotification(this);
        return START_REDELIVER_INTENT;
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
        this.unregisterReceiver(mConnectivityChangeReceiver);
        if (mGoogleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
            mGoogleApiClient.disconnect();
        }
        stopForeground(true);
        super.onDestroy();
    }

    private synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.d(TAG, "Google play services connected");
        LocationRequest locationRequest = new LocationRequest();
        locationRequest.setInterval(mInterval);
        locationRequest.setFastestInterval(mInterval);
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, locationRequest, this);
    }

    @Override
    public void onConnectionSuspended(int i) {
        // The connection to Google Play services was lost for some reason. We call connect() to
        // attempt to re-establish the connection.
        Log.i(TAG, "Connection suspended");
        mGoogleApiClient.connect();
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.i(TAG, "Connection failed: ConnectionResult.getErrorCode() = " + connectionResult.getErrorCode());
    }

    @Override
    public void onLocationChanged(Location location) {
        PostLocationServiceManager.get().postLocation(mAuthToken, location, new Callback<PostLocationResponse>() {
            @Override
            public void onResponse(Call<PostLocationResponse> call, Response<PostLocationResponse> response) {
                Log.d(TAG, "Successfully posted location");
                if (mIsTimingOut) {
                    // We resumed service from a previous failure; emit an event
                    Log.d(TAG, "Network timeout ended, broadcasting event");
                    mIsTimingOut = false;
                    broadcastTimeoutEvent(false);
                }

                int driverMode = -1;
                String deliveryType = "";
                if (response.body() != null) {
                    driverMode = response.body().driverMode;
                    deliveryType = response.body().deliveryType;
                }

                broadcastResponseInfo(call.request().url().toString(), response.code(), driverMode, deliveryType);
            }

            @Override
            public void onFailure(Call<PostLocationResponse> call, Throwable t) {
                Log.d(TAG, "Location post failure: " + t.getMessage());
                if ((t instanceof SocketTimeoutException || t instanceof UnknownHostException)
                        && !mIsTimingOut) {
                    Log.d(TAG, "Network timeout, broadcasting event");
                    // We encountered a timeout; emit an event
                    mIsTimingOut = true;
                    broadcastTimeoutEvent(true);
                }
                broadcastResponseInfo(call.request().url().toString(), -1, -1, t.getMessage());
            }
        });
    }

    /**
     * Emit a timeout event to the app.
     * @param isTimeout True if we encountered a timeout, false if we resumed service following a timeout.
     */
    private void broadcastTimeoutEvent(boolean isTimeout) {
        Intent timeoutIntent = new Intent(EVENT_LOCATION_TIMEOUT);
        timeoutIntent.putExtra(EXTRA_TIMEOUT, isTimeout);
        LocalBroadcastManager.getInstance(LocationTimerService.this)
                .sendBroadcast(timeoutIntent);
    }

    /**
     * Send the last url for /track and the status code back to the app.
     * @param url
     * @param statusCode
     */
    private void broadcastResponseInfo(String url, int statusCode, int driverMode, String deliveryType, @Nullable String exceptionText) {

        Intent responseIntent = new Intent(EVENT_LOCATION_RESPONSE);
        responseIntent.putExtra(EXTRA_RESPONSE_STATUS, statusCode);
        responseIntent.putExtra(EXTRA_RESPONSE_URL, url);
        responseIntent.putExtra(EXTRA_RESPONSE_DRIVERMODE, driverMode);
        responseIntent.putExtra(EXTRA_RESPONSE_DELIVERY_TYPE, deliveryType);
        if (!TextUtils.isEmpty(exceptionText)) {
            responseIntent.putExtra(EXTRA_RESPONSE_EXCEPTION, exceptionText);
        }
        LocalBroadcastManager.getInstance(LocationTimerService.this).sendBroadcast(responseIntent);
    }

    private void broadcastResponseInfo(String url, int statusCode, int driverMode, String deliveryType) {
        broadcastResponseInfo(url, statusCode, driverMode, deliveryType, null);
    }

    public static String getNowAsIso8601() {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault());
        df.setTimeZone(TimeZone.getTimeZone("gmt"));
        return df.format(new Date());
    }

    /**
     * Make the foreground service notification for Android Oreo compatibility
     * @param context The service which will be running
     */
    public static void makeForegroundServiceNotification(Service context) {
        if (android.os.Build.VERSION.SDK_INT >= 26) {
            // For android Oreo, we need to display a foreground notification to allow background polling
            CharSequence msg = context.getText(R.string.notification_msg);
            Intent notificationIntent = new Intent(context, getMainActivityClass(context));
            PendingIntent pendingIntent =
                    PendingIntent.getActivity(context, 0, notificationIntent, 0);
            Notification notification = new NotificationCompat.Builder(context)
                    .setContentTitle(context.getText(R.string.notification_title))
                    .setContentText(msg)
                    .setSmallIcon(R.drawable.eaze_notification_logo)
                    .setContentIntent(pendingIntent)
                    .setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
                    .build();
            context.startForeground(NOTIFICATION_ID, notification);
        }
    }

    @Nullable
    /**
     * Private helper method to return the main activity class for setting on the content intent.
     */
    private static Class getMainActivityClass(Context context) {
        String packageName = context.getPackageName();
        Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
        String className = launchIntent.getComponent().getClassName();
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}
