package com.github.andlyticsproject.console.v2;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
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.AppAccessBlockedException;
import com.github.andlyticsproject.console.AuthenticationException;
import com.github.andlyticsproject.console.DevConsoleException;
import com.github.andlyticsproject.model.DeveloperConsoleAccount;
import com.github.andlyticsproject.util.FileUtils;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.http.cookie.Cookie;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public abstract class BaseAuthenticator implements DevConsoleAuthenticator {

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

	protected static final Pattern STARTUP_DATA_PATTERN = Pattern
			.compile("startupData = (\\{.+?\\});");

	protected String accountName;

	protected BaseAuthenticator(String accountName) {
		this.accountName = accountName;
	}

	protected String findXsrfToken(JSONObject startupData) {
		try {
			return new JSONObject(startupData.getString("XsrfToken")).getString("1");
		} catch (JSONException e) {
			throw new DevConsoleException(e);
		}
	}

	protected DeveloperConsoleAccount[] findDeveloperAccounts(JSONObject startupData) {
		List<DeveloperConsoleAccount> devAccounts = new ArrayList<DeveloperConsoleAccount>();

		try {
			JSONObject devConsoleAccountsObj = new JSONObject(
					startupData.getString("DeveloperConsoleAccounts"));
			JSONArray devConsoleAccountsArr = devConsoleAccountsObj.getJSONArray("1");
			for (int i = 0; i < devConsoleAccountsArr.length(); i++) {
				JSONObject accountObj = devConsoleAccountsArr.getJSONObject(i);
				String developerId = accountObj.getString("1");
				String developerName = StringEscapeUtils.unescapeJava(accountObj.getString("2"));
				// Cannot access apps if e.g. a developer agreement needs to be accepted
				// XXX seems to be always false? Disable check for now
				//				boolean canAccessApps = accountObj.getBoolean("3");
				boolean canAccessApps = true;
				devAccounts.add(new DeveloperConsoleAccount(developerId, developerName, canAccessApps));
			}


			return devAccounts.isEmpty() ? null : devAccounts
					.toArray(new DeveloperConsoleAccount[devAccounts.size()]);
		} catch (JSONException e) {
			throw new DevConsoleException(e);
		}
	}

	protected List<String> findWhitelistedFeatures(JSONObject startupData) {
		List<String> result = new ArrayList<String>();

		try {
			JSONArray featuresArr = new JSONObject(startupData.getString("WhitelistedFeatures"))
					.getJSONArray("1");
			for (int i = 0; i < featuresArr.length(); i++) {
				result.add(featuresArr.getString(i));
			}

			return Collections.unmodifiableList(result);
		} catch (JSONException e) {
			throw new DevConsoleException(e);
		}
	}

	public JSONObject getStartupData(String responseStr) {
		try {
			Matcher m = STARTUP_DATA_PATTERN.matcher(responseStr);
			if (m.find()) {
				String startupDataStr = m.group(1);
				return new JSONObject(startupDataStr);
			}

			return null;
		} catch (JSONException e) {
			throw new DevConsoleException(e);
		}
	}

	protected String findPreferredCurrency(JSONObject startupData) {
		// fallback
		String result = "USD";

		try {
			JSONObject userDetails = new JSONObject(startupData.getString("UserDetails"));
			if (userDetails.has("2")) {
				result = userDetails.getString("2");
			}

			return result;
		} catch (JSONException e) {
			throw new DevConsoleException(e);
		}
	}

	public String getAccountName() {
		return accountName;
	}

	protected void debugAuthFailure(String responseStr, String webloginUrl) {
		FileUtils.writeToAndlyticsDir("console-response.html", responseStr);
		openAuthUrlInBrowser(webloginUrl);
	}

	protected void openAuthUrlInBrowser(String webloginUrl) {
		if (webloginUrl == null) {
			Log.d(TAG, "Null webloginUrl?");
			return;
		}

		Log.d(TAG, "Opening login URL in browser: " + webloginUrl);

		Intent viewInBrowser = new Intent(Intent.ACTION_VIEW);
		viewInBrowser.setData(Uri.parse(webloginUrl));

		// Always show the notification
		// When this occurs, it can often occur in batches, e.g. if a the user also clicks to view
		// comments which results in multiple dev consoles opening in their browser without an
		// explanation. This is even worse if they have multiple accounts and/or are currently
		// signed in via a different account
		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_open_browser));
		builder.setAutoCancel(true);
		PendingIntent contentIntent = PendingIntent.getActivity(ctx, accountName.hashCode(),
				viewInBrowser, PendingIntent.FLAG_UPDATE_CURRENT);
		builder.setContentIntent(contentIntent);

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

	protected SessionCredentials createSessionCredentials(String accountName, String webloginUrl,
			String responseStr, List<Cookie> cookies) {
		JSONObject startupData = getStartupData(responseStr);
		if (startupData == null) {
			debugAuthFailure(responseStr, webloginUrl);

			throw new AuthenticationException("Couldn't find StartupData JSON object.");
		}

		DeveloperConsoleAccount[] developerAccounts = findDeveloperAccounts(startupData);
		if (developerAccounts == null) {
			debugAuthFailure(responseStr, webloginUrl);

			throw new AuthenticationException("Couldn't get developer account ID.");
		}
		boolean allowedToAccessAppsForSomeAccounts = false;
		for (DeveloperConsoleAccount account : developerAccounts) {
			if (account.getCanAccessApps()) {
				allowedToAccessAppsForSomeAccounts = true;
			} else {
				// TODO Report this to the user properly, but don't spam them because they may
				// never be able to resolve the problem e.g. the account owner needs to agree to new terms
				Log.w(TAG, "Not allowed to fetch app info for " + account.getDeveloperId() + ". " +
						"Log into the account via your browser to resolve the problem.");
			}
		}
		if (!allowedToAccessAppsForSomeAccounts) {
			throw new AppAccessBlockedException("Not allowed to fetch app info for any account.");
		}

		String xsrfToken = findXsrfToken(startupData);
		if (xsrfToken == null) {
			debugAuthFailure(responseStr, webloginUrl);

			throw new AuthenticationException("Couldn't get XSRF token.");
		}

		List<String> whitelistedFeatures = findWhitelistedFeatures(startupData);

		String preferredCurrency = findPreferredCurrency(startupData);

		SessionCredentials result = new SessionCredentials(accountName, xsrfToken,
				developerAccounts);
		result.addCookies(cookies);
		result.addWhitelistedFeatures(whitelistedFeatures);
		result.setPreferredCurrency(preferredCurrency);

		return result;
	}

}
