package com.github.andlyticsproject.console.v2;

import android.annotation.SuppressLint;

import com.github.andlyticsproject.console.DevConsoleProtocolException;
import com.github.andlyticsproject.model.AppInfo;
import com.github.andlyticsproject.model.AppStats;
import com.github.andlyticsproject.model.Comment;
import com.github.andlyticsproject.model.RevenueSummary;
import com.github.andlyticsproject.util.FileUtils;
import com.google.gson.JsonObject;

import org.apache.http.client.methods.HttpPost;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

@SuppressLint("DefaultLocale")
public class DevConsoleV2Protocol {

	// Base urls
	static final String URL_DEVELOPER_CONSOLE = "https://play.google.com/apps/publish";
	static final String URL_APPS = DevConsoleV2Protocol.URL_DEVELOPER_CONSOLE + "/androidapps";
	static final String URL_STATISTICS = DevConsoleV2Protocol.URL_DEVELOPER_CONSOLE + "/statistics";
	static final String URL_REVIEWS = DevConsoleV2Protocol.URL_DEVELOPER_CONSOLE + "/reviews";

	// Templates for payloads used in POST requests
	static final String FETCH_APPS_TEMPLATE = "{\"method\":\"fetch\","
			+ "\"params\":{\"2\":1,\"3\":7},\"xsrf\":\"%s\"}";
	// 1$: comma separated list of package names
	static final String FETCH_APPS_BY_PACKAGES_TEMPLATE = "{\"method\":\"fetch\","
			+ "\"params\":{\"1\":[%1$s],\"3\":1},\"xsrf\":\"%2$s\"}";
	// 1$: package name, 2$: XSRF
	static final String FETCH_APP_TEMPLATE = "{\"method\":\"fetch\","
			+ "\"params\":{\"1\":[\"%1$s\"],\"3\":0},\"xsrf\":\"%2$s\"}";
	// 1$: package name, 2$: XSRF
	static final String GET_RATINGS_TEMPLATE = "{\"method\":\"getRatings\","
			+ "\"params\":{\"1\":[\"%1$s\"]},\"xsrf\":\"%2$s\"}";
	// 1$: package name, 2$: start, 3$: num comments to fetch, 4$: display locale, 5$ XSRF
	static final String GET_REVIEWS_TEMPLATE = "{\"method\":\"getReviews\","
			+ "\"params\":{\"1\":\"%1$s\",\"2\":%2$d,\"3\":%3$d,\"8\":\"%4$s\",\"10\":0,\"18\":1},\"xsrf\":\"%5$s\"}";
	// 1$: package name, 2$: stats type, 3$: stats by, 4$: XSRF
	static final String GET_COMBINED_STATS_TEMPLATE = "{\"method\":\"getCombinedStats\","
			+ "\"params\":{\"1\":\"%1$s\",\"2\":1,\"3\":%2$d,\"4\":[%3$d]},\"xsrf\":\"%4$s\"}";
	// %1$s: package name, %2$s: XSRF
	static final String REVENUE_SUMMARY_TEMPLATE = "{\"method\":\"revenueSummary\",\"params\":{\"1\":\"%1$s\",\"2\":\"\"},\"xsrf\":\"%2$s\"}";
	// %1$s: package name, %2$s: XSRF
	static final String REVENUE_HISTORICAL_DATA = "{\"method\":\"historicalData\",\"params\":{\"1\":\"%1$s\",\"2\":\"\"},\"xsrf\":\"%2$s\"}";

	static final String REPLY_TO_COMMENTS_FEATURE = "REPLY_TO_COMMENTS";

	// Represents the different ways to break down statistics by e.g. by android
	// version
	static final int STATS_BY_ANDROID_VERSION = 1;
	static final int STATS_BY_DEVICE = 2;
	static final int STATS_BY_COUNTRY = 3;
	static final int STATS_BY_LANGUAGE = 4;
	static final int STATS_BY_APP_VERSION = 5;
	static final int STATS_BY_CARRIER = 6;

	// Represents the different types of statistics e.g. active device installs
	static final int STATS_TYPE_ACTIVE_DEVICE_INSTALLS = 1;
	static final int STATS_TYPE_TOTAL_USER_INSTALLS = 8;

	static final int COMMENT_REPLY_MAX_LENGTH = 350;

	private SessionCredentials sessionCredentials;

	DevConsoleV2Protocol() {
	}

	DevConsoleV2Protocol(SessionCredentials sessionCredentials) {
		this.sessionCredentials = sessionCredentials;
	}

	SessionCredentials getSessionCredentials() {
		return sessionCredentials;
	}

	void setSessionCredentials(SessionCredentials sessionCredentials) {
		this.sessionCredentials = sessionCredentials;
	}

	boolean hasSessionCredentials() {
		return sessionCredentials != null;
	}

	void invalidateSessionCredentials() {
		sessionCredentials = null;
	}

	private void checkState() {
		if (sessionCredentials == null) {
			throw new IllegalStateException("Set session credentials first.");
		}
	}

	void addHeaders(HttpPost post, String developerId) {
		checkState();

		post.addHeader("Host", "play.google.com");
		post.addHeader("Connection", "keep-alive");
		post.addHeader("Content-Type", "application/javascript; charset=UTF-8");
		// XXX get this dynamically by fetching and executing the nocache.js file:
		// https://play.google.com/apps/publish/v2/gwt/com.google.wireless.android.vending.developer.fox.Fox.nocache.js
		post.addHeader("X-GWT-Permutation", "7E419416D8BA779A68D417481802D188");
		post.addHeader("Origin", "https://play.google.com");
		post.addHeader("X-GWT-Module-Base", "https://play.google.com/apps/publish/gwt/");
		post.addHeader("Referer", "https://play.google.com/apps/publish/?dev_acc=" + developerId);
	}

	String createDeveloperUrl(String baseUrl, String developerId) {
		checkState();

		return String.format("%s?dev_acc=%s", baseUrl, developerId);
	}

	String createFetchAppsUrl(String developerId) {
		return createDeveloperUrl(URL_APPS, developerId);
	}

	String createFetchStatisticsUrl(String developerId) {
		return createDeveloperUrl(URL_STATISTICS, developerId);
	}

	String createCommentsUrl(String developerId) {
		return createDeveloperUrl(URL_REVIEWS, developerId);
	}

	String createRevenueUrl(String developerId) {
		return createDeveloperUrl(URL_STATISTICS, developerId);
	}

	String createFetchAppInfosRequest() {
		checkState();

		// TODO Check the remaining possible parameters to see if they are
		// needed for large numbers of apps
		return String.format(FETCH_APPS_TEMPLATE, sessionCredentials.getXsrfToken());
	}

	String createFetchAppInfosRequest(List<String> packages) {
		checkState();

		StringBuilder buff = new StringBuilder();
		for (int i = 0; i < packages.size(); i++) {
			String packageName = packages.get(i);
			buff.append(packageName);
			if (i != packages.size() - 1) {
				buff.append(",");
			}
		}
		String packageList = buff.toString();

		return String.format(FETCH_APPS_BY_PACKAGES_TEMPLATE, packageList,
				sessionCredentials.getXsrfToken());
	}

	List<AppInfo> parseAppInfosResponse(String json, String accountName, boolean skipIncomplete) {
		try {
			return JsonParser.parseAppInfos(json, accountName, skipIncomplete);
		} catch (JSONException ex) {
			saveDebugJson(json);
			throw new DevConsoleProtocolException(json, ex);
		}
	}

	private static void saveDebugJson(String json) {
		FileUtils.tryWriteToDebugDir(
				String.format("console_reply_%d.json", System.currentTimeMillis()), json);
	}

	String createFetchAppInfoRequest(String packageName) {
		checkState();

		return String.format(FETCH_APP_TEMPLATE, packageName, sessionCredentials.getXsrfToken());
	}

	String createFetchStatisticsRequest(String packageName, int statsType) {
		checkState();

		// Don't care about the breakdown at the moment:
		// STATS_BY_ANDROID_VERSION
		return String.format(GET_COMBINED_STATS_TEMPLATE, packageName, statsType,
				STATS_BY_ANDROID_VERSION, sessionCredentials.getXsrfToken());
	}

	void parseStatisticsResponse(String json, AppStats stats, int statsType) {
		try {
			JsonParser.parseStatistics(json, stats, statsType);
		} catch (JSONException ex) {
			saveDebugJson(json);
			throw new DevConsoleProtocolException(json, ex);
		}
	}

	String createFetchRatingsRequest(String packageName) {
		checkState();

		return String.format(GET_RATINGS_TEMPLATE, packageName, sessionCredentials.getXsrfToken());
	}

	void parseRatingsResponse(String json, AppStats stats) {
		try {
			JsonParser.parseRatings(json, stats);
		} catch (JSONException ex) {
			saveDebugJson(json);
			throw new DevConsoleProtocolException(json, ex);
		}
	}

	String createFetchCommentsRequest(String packageName, int start, int pageSize,
			String displayLocale) {
		checkState();

		return String.format(GET_REVIEWS_TEMPLATE, packageName, start, pageSize, displayLocale,
				sessionCredentials.getXsrfToken());
	}

	String createReplyToCommentRequest(String packageName, String commentId, String reply) {
		checkState();

		if (!canReplyToComments()) {
			throw new IllegalStateException(
					"Reply to comments feature not available for this account");
		}

		// XXX we can probably do better, truncate for now
		if (reply.length() > COMMENT_REPLY_MAX_LENGTH) {
			reply = reply.substring(0, COMMENT_REPLY_MAX_LENGTH);
		}

		JsonObject replyObj = new JsonObject();
		replyObj.addProperty("method", "sendReply");
		JsonObject params = new JsonObject();
		params.addProperty("1", packageName);
		params.addProperty("2", commentId);
		params.addProperty("3", reply);
		replyObj.add("params", params);
		replyObj.addProperty("xsrf", sessionCredentials.getXsrfToken());

		return replyObj.toString();
	}

	boolean hasFeature(String feature) {
		checkState();

		return sessionCredentials.hasFeature(feature);
	}

	boolean canReplyToComments() {
		// this has apparently been removed because now everybody can 
		// reply to comments
		//		return hasFeature(REPLY_TO_COMMENTS_FEATURE);
		return true;
	}

	List<Comment> parseCommentsResponse(String json) {
		try {
			return JsonParser.parseComments(json);
		} catch (JSONException ex) {
			saveDebugJson(json);
			throw new DevConsoleProtocolException(json, ex);
		}
	}

	Comment parseCommentReplyResponse(String json) {
		try {
			return JsonParser.parseCommentReplyResponse(json);
		} catch (JSONException ex) {
			saveDebugJson(json);
			throw new DevConsoleProtocolException(json, ex);
		}
	}

	String createFetchRevenueSummaryRequest(String packageName) {
		checkState();

		try {
			JSONObject jsonObj = new JSONObject();
			jsonObj.put("method", "fetchStats");
			jsonObj.put("xsrf", sessionCredentials.getXsrfToken());

			JSONObject paramsObj = new JSONObject();
			jsonObj.put("params", paramsObj);

			JSONArray paramOne = new JSONArray();
			paramsObj.put("1", paramOne);

			JSONObject firstElem = new JSONObject();
			firstElem.put("1", new JSONObject().put("1", packageName).put("2", "1"));
			firstElem.put("2", -1);
			firstElem.put("3", -1);

			JSONArray arr = new JSONArray();
			arr.put(new JSONObject().put("1", 11).put("2",
					new JSONArray().put(sessionCredentials.getPreferredCurrency())));
			arr.put(new JSONObject().put("1", 18).put("2",
			// summary, last day, last 7, last 30
					new JSONArray().put("-1").put("1").put("7").put("30")));
			firstElem.put("6", arr);

			firstElem.put("7", new JSONArray().put(18));
			firstElem.put("8", new JSONArray().put(17));

			paramOne.put(firstElem);

			return jsonObj.toString();
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
	}

	RevenueSummary parseRevenueResponse(String json) {
		try {
			return JsonParser.parseRevenueResponse(json, sessionCredentials.getPreferredCurrency());
		} catch (JSONException ex) {
			saveDebugJson(json);
			throw new DevConsoleProtocolException(json, ex);
		}
	}

	String createFetchHistoricalRevenueRequest(String packageName) {
		checkState();

		return String.format(REVENUE_HISTORICAL_DATA, packageName,
				sessionCredentials.getXsrfToken());
	}

}
