package com.github.andlyticsproject.console.v2;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log;

import com.github.andlyticsproject.AndlyticsApp;
import com.github.andlyticsproject.R;
import com.github.andlyticsproject.console.AuthenticationException;
import com.github.andlyticsproject.console.NetworkException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;

public class AccountManagerAuthenticator extends BaseAuthenticator {

	private static final String TAG = AccountManagerAuthenticator.class.getSimpleName();

	private static final String DEVELOPER_CONSOLE_URL = "https://play.google.com/apps/publish/";
	private static final Uri ISSUE_AUTH_TOKEN_URL = Uri
			.parse("https://www.google.com/accounts/IssueAuthToken?service=gaia&Session=false");
	private static final Uri TOKEN_AUTH_URL = Uri
			.parse("https://www.google.com/accounts/TokenAuth");

	private static final int REQUEST_AUTHENTICATE = 42;

	private static final boolean DEBUG = false;

	private AccountManager accountManager;

	// includes one-time token
	private String webloginUrl;

	private DefaultHttpClient httpClient;

	public AccountManagerAuthenticator(String accountName, DefaultHttpClient httpClient) {
		super(accountName);
		this.accountManager = AccountManager.get(AndlyticsApp.getInstance());
		this.httpClient = httpClient;
	}

	// as described here: http://www.slideshare.net/pickerweng/chromium-os-login
	// http://www.chromium.org/chromium-os/chromiumos-design-docs/login
	// and implemented by the Android Browser:
	// packages/apps/Browser/src/com/android/browser/GoogleAccountLogin.java
	// packages/apps/Browser/src/com/android/browser/DeviceAccountLogin.java
	@Override
	public SessionCredentials authenticate(Activity activity, boolean invalidate)
			throws AuthenticationException {
		return authenticateInternal(activity, invalidate);
	}

	@Override
	public SessionCredentials authenticateSilently(boolean invalidate)
			throws AuthenticationException {
		return authenticateInternal(null, invalidate);
	}

	@SuppressWarnings("deprecation")
	private SessionCredentials authenticateInternal(Activity activity, boolean invalidate)
			throws AuthenticationException {
		try {
			Account[] accounts = accountManager.getAccountsByType("com.google");
			Account account = null;
			for (Account acc : accounts) {
				if (acc.name.equals(accountName)) {
					account = acc;
					break;
				}
			}
			if (account == null) {
				throw new AuthenticationException(String.format("Account %s not found on device?",
						accountName));
			}

			if (invalidate && webloginUrl != null) {
				// probably not needed, since what we are getting is a very
				// short-lived token
				accountManager.invalidateAuthToken(account.type, webloginUrl);
			}

			Bundle authResult = accountManager.getAuthToken(account,
					"weblogin:service=androiddeveloper&continue=" + DEVELOPER_CONSOLE_URL, false,
					null, null).getResult();
			if (authResult.containsKey(AccountManager.KEY_INTENT)) {
				Intent authIntent = authResult.getParcelable(AccountManager.KEY_INTENT);
				if (DEBUG) {
					Log.w(TAG, "Got a reauthenticate intent: " + authIntent);
				}

				// silent mode, show notification
				if (activity == null) {
					Context ctx = AndlyticsApp.getInstance();
					Builder builder = new NotificationCompat.Builder(ctx);
					builder.setSmallIcon(R.drawable.statusbar_andlytics);
					builder.setContentTitle(ctx.getResources().getString(R.string.auth_error,
							accountName));
					builder.setContentText(ctx.getResources().getString(R.string.auth_error,
							accountName));
					builder.setAutoCancel(true);
					PendingIntent contentIntent = PendingIntent.getActivity(ctx,
							accountName.hashCode(), authIntent, PendingIntent.FLAG_UPDATE_CURRENT);
					builder.setContentIntent(contentIntent);

					NotificationManager nm = (NotificationManager) ctx
							.getSystemService(Context.NOTIFICATION_SERVICE);
					nm.notify(accountName.hashCode(), builder.build());

					return null;
				}

				// activity mode, start activity
				authIntent.setFlags(authIntent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
				activity.startActivityForResult(authIntent, REQUEST_AUTHENTICATE);

				return null;
			}
			webloginUrl = authResult.getString(AccountManager.KEY_AUTHTOKEN);
			if (webloginUrl == null) {
				throw new AuthenticationException(
						"Unexpected authentication error: weblogin URL = null");
			}
			if (DEBUG) {
				Log.d(TAG, "Weblogin URL: " + webloginUrl);
			}

			if (!webloginUrl.contains("MergeSession") || webloginUrl.contains("WILL_NOT_SIGN_IN")) {
				Log.d(TAG, "Most probably additional verification is required, "
						+ "opening browser");

				String sid = accountManager
						.getAuthToken(account, "SID", null, activity, null, null).getResult()
						.getString(AccountManager.KEY_AUTHTOKEN);
				if (sid == null) {
					throw new AuthenticationException("Authentication error: cannot get SID.");
				}
				String lsid = accountManager
						.getAuthToken(account, "LSID", null, activity, null, null).getResult()
						.getString(AccountManager.KEY_AUTHTOKEN);
				if (lsid == null) {
					throw new AuthenticationException("Authentication error: cannot get LSID.");
				}

				String url = ISSUE_AUTH_TOKEN_URL.buildUpon().appendQueryParameter("SID", sid)
						.appendQueryParameter("LSID", lsid).build().toString();
				HttpPost getUberToken = new HttpPost(url);
				HttpResponse response = httpClient.execute(getUberToken);
				int status = response.getStatusLine().getStatusCode();
				if (status == HttpStatus.SC_UNAUTHORIZED) {
					throw new AuthenticationException("Cannot get uber token: "
							+ response.getStatusLine());
				}
				String uberToken = EntityUtils.toString(response.getEntity(), "UTF-8");
				if (uberToken == null || "".equals(uberToken)) {
					throw new AuthenticationException("Invalid ubertoken");
				}
				if (DEBUG) {
					Log.d(TAG, "Uber token: " + uberToken);
				}

				webloginUrl = TOKEN_AUTH_URL.buildUpon()
						.appendQueryParameter("source", "android-browser")
						.appendQueryParameter("auth", uberToken)
						.appendQueryParameter("continue", DEVELOPER_CONSOLE_URL).build().toString();
			}

			HttpGet getConsole = new HttpGet(webloginUrl);
			HttpResponse response = httpClient.execute(getConsole);
			int status = response.getStatusLine().getStatusCode();
			if (status == HttpStatus.SC_UNAUTHORIZED) {
				throw new AuthenticationException("Authentication token expired: "
						+ response.getStatusLine());
			}
			if (status != HttpStatus.SC_OK) {
				throw new AuthenticationException("Authentication error: "
						+ response.getStatusLine());
			}
			HttpEntity entity = response.getEntity();
			if (entity == null) {
				throw new AuthenticationException("Authentication error: null result?");
			}

			String responseStr = EntityUtils.toString(entity, "UTF-8");
			if (DEBUG) {
				Log.d(TAG, "Response: " + responseStr);
			}

			return createSessionCredentials(accountName, webloginUrl, responseStr,
					httpClient.getCookieStore().getCookies());
		} catch (IOException e) {
			throw new NetworkException(e);
		} catch (OperationCanceledException e) {
			throw new AuthenticationException(e);
		} catch (AuthenticatorException e) {
			throw new AuthenticationException(e);
		}
	}

}
