package com.github.andlyticsproject.admob;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;

import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;

import com.github.andlyticsproject.AndlyticsApp;
import com.github.andlyticsproject.ContentAdapter;
import com.github.andlyticsproject.Preferences.Timeframe;
import com.github.andlyticsproject.console.NetworkException;
import com.github.andlyticsproject.model.AdmobStats;

public class AdmobRequest {

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

	public static final String ERROR_TOKEN_INVALID = "token_invalid";
	public static final String ERROR_INVALID_SITE_ID = "site_id_invalid";
	public static final String ERROR_RATE_LIMIT_EXCEEDED = "rate_limit_exceeded";
	public static final String ERROR_NETWORK_ERROR = "andlytics_network_error";
	public static final String ERROR_ACCOUNT_REMOVED = "andlytics_account_removed";
	public static final String ERROR_ASK_USER_PASSWORD = "andlytics_account_ask_for_password";
	public static final String ERROR_REQUEST_INVALID = "request_invalid";

	public static final String KEY_SITE_ID = "site_id";
	public static final String KEY_REQUESTS = "requests";
	public static final String KEY_HOUSEAD_REQUESTS = "housead_requests";
	public static final String KEY_INTERSTITIAL_REQUESTS = "interstitial_requests";
	public static final String KEY_IMPRESSIONS = "impressions";
	public static final String KEY_FILL_RATE = "fill_rate";
	public static final String KEY_HOUSEAD_FILL_RATE = "housead_fill_rate";
	public static final String KEY_OVERALL_FILL_RATE = "overall_fill_rate";
	public static final String KEY_CLICKS = "clicks";
	public static final String KEY_HOUSEAD_CLICKS = "housead_clicks";
	public static final String KEY_CTR = "ctr";
	public static final String KEY_ECPM = "ecpm";
	public static final String KEY_REVENUE = "revenue";
	public static final String KEY_CPC_REVENUE = "cpc_revenue";
	public static final String KEY_CPM_REVENUE = "cpm_revenue";
	public static final String KEY_EXCHANGE_DOWNLOADS = "exchange_downloads";
	public static final String KEY_DATE = "date";

	@SuppressLint("SimpleDateFormat")
	private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

	private static final boolean DEBUG = false;

	//base access url to admob api
	private static final String BASE_URL = "https://api.admob.com/v2/";
	private static final String clientKey = "k1541c79203abfb693edb05ede83891b";

	public static final long RETRY_TIME = 4000;

	//construct
	public AdmobRequest() {
	}

	//user login
	public static String login(String email, String password) throws NetworkException,
			AdmobInvalidTokenException, AdmobGenericException, AdmobInvalidRequestException,
			AdmobRateLimitExceededException {

		String token = null;

		//create the parameters
		@SuppressWarnings("deprecation")
		String params = "client_key=" + clientKey + "&email=" + URLEncoder.encode(email)
				+ "&password=" + URLEncoder.encode(password);

		try {
			//try to login
			if (DEBUG)
				Log.d(TAG, email + " admob login request");
			JSONArray data = getResponse("auth", "login", params, true, true);

			//decode the json response and assign token for future use in this session
			token = data.getJSONObject(0).getString("token");
		} catch (JSONException e) {
			throw new AdmobGenericException(e);
		}

		return token;
	}

	private static void handleNonJsonException(Exception e, String sb) throws NetworkException,
			AdmobGenericException, AdmobInvalidTokenException {

		if (e instanceof SocketException || e instanceof UnknownHostException
				|| e instanceof IOException || e instanceof NetworkException) {

			throw new NetworkException(e);
		} else if (e instanceof AdmobInvalidTokenException) {
			throw (AdmobInvalidTokenException) e;
		} else {
			throw new AdmobGenericException(e, sb);
		}
	}

	public static JSONArray getData(Context context, String account, String token, String object,
			String method, String[] requestData) throws AdmobAccountRemovedException,
			NetworkException, AdmobRateLimitExceededException, AdmobInvalidTokenException,
			AdmobGenericException, AdmobAskForPasswordException, AdmobInvalidRequestException {

		String params = createRequestParams(token, requestData);

		//pass everything to getResponse and return it
		JSONArray result = null;
		try {
			result = getResponse(object, method, params, false, true);

		} catch (AdmobInvalidTokenException e) {

			// token invalid, maybe too old, retry with new token
			invalidateAdmobToken(account, token, context);
			String newToken = authenticateAdmobAccount(account, context);
			params = createRequestParams(newToken, requestData);
			result = getResponse(object, method, params, false, true);
		}

		return result;

	}

	private static String createRequestParams(String token, String[] requestData) {
		//init params
		String params = "client_key=" + clientKey;
		params += "&token=" + token;

		//build the params for getResponse to use
		for (String row : requestData) {
			params = params + "&" + row;
		}
		return params;
	}

	//private method to handle data requests
	private static JSONArray getResponse(String object, String method, String urlParameters,
			boolean sendPost, boolean retry) throws NetworkException,
			AdmobRateLimitExceededException, AdmobInvalidTokenException, AdmobGenericException,
			AdmobInvalidRequestException {

		int responseCode = -1;
		JSONObject result = null;
		String sb = null;
		try {

			//create the sub url
			String subUrl = BASE_URL + object + "/" + method;

			HttpsURLConnection con = null;

			//check if it needs to be post or get
			if (sendPost) {

				con = (HttpsURLConnection) new URL(subUrl).openConnection();
				con.setHostnameVerifier(new BrowserCompatHostnameVerifier());
				// create a connection
				con.setDoOutput(true);// allow for posting data to the connection

				// pass data to the connection (POST)
				OutputStream post = con.getOutputStream();
				BufferedWriter out = new BufferedWriter(new OutputStreamWriter(post));
				out.write(urlParameters);
				out.close();

			} else {
				if (DEBUG)
					Log.d(TAG, subUrl);
				con = (HttpsURLConnection) new URL(subUrl + "?" + urlParameters).openConnection();
				con.setHostnameVerifier(new BrowserCompatHostnameVerifier());
				//System.out.println("admob request: " + subUrl + "?" + urlParameters);
				//open the link and post everything to it

			}

			//read the response

			InputStream resultStream = con.getInputStream();
			BufferedReader aReader = new java.io.BufferedReader(new java.io.InputStreamReader(
					resultStream));
			StringBuffer aResponse = new StringBuffer();
			String aLine = aReader.readLine();
			while (aLine != null) {
				aResponse.append(aLine + "\n");
				aLine = aReader.readLine();
			}
			resultStream.close();
			sb = aResponse.toString();

			if (con instanceof HttpURLConnection) {
				HttpURLConnection httpConnection = (HttpURLConnection) con;

				responseCode = httpConnection.getResponseCode();
			}

			if (sb == null || "".equals(sb.trim())) {
				Log.w(TAG, "admob empty respsonse" + " token invalid?");
				throw new AdmobInvalidTokenException(responseCode + " null response");
			} else if (!sb.startsWith("{")) {
				throw new AdmobInvalidTokenException(responseCode + " invalid response: " + sb);
			}

			result = new JSONObject(sb);

			//  } catch (IOException e) {
			//    throw new NetworkException(e);
		} catch (JSONException e) {
			handleNonJsonException(e, responseCode + " " + sb);
		} catch (Exception e) {
			handleNonJsonException(e, responseCode + " " + sb);
		}

		// check json for errors
		try {
			checkJsonErrors(result);
		} catch (AdmobRateLimitExceededException e) {
			if (retry) {
				Log.w(TAG, "admob rateLimitExceeded, retry in 5 sec...");
				try {
					Thread.sleep(AdmobRequest.RETRY_TIME);
				} catch (InterruptedException e1) {
				}
				return getResponse(object, method, urlParameters, sendPost, false);
			} else {
				throw e;
			}
		}

		JSONArray dataObject = toJsonArray(result, "data");

		return dataObject;

	}

	private static void checkJsonErrors(JSONObject result) throws AdmobInvalidTokenException,
			AdmobRateLimitExceededException, AdmobGenericException, AdmobInvalidRequestException {

		Map<String, String> errorMap = new HashMap<String, String>();

		JSONArray errors = (result).optJSONArray("errors");

		if (errors != null && errors.length() > 0) {

			for (int i = 0; i < errors.length(); i++) {

				try {
					String code = errors.getJSONObject(i).optString("code");
					String message = errors.getJSONObject(i).optString("msg");
					errorMap.put(code, message);

				} catch (JSONException e) {
					throw new AdmobGenericException(e);
				}
			}
		}

		if (errorMap.size() > 0) {

			if (errorMap.containsKey(ERROR_RATE_LIMIT_EXCEEDED)) {

				String exMessage = ERROR_RATE_LIMIT_EXCEEDED + " "
						+ errorMap.get(ERROR_RATE_LIMIT_EXCEEDED);
				throw new AdmobRateLimitExceededException(exMessage);

			} else if (errorMap.containsKey(ERROR_REQUEST_INVALID)) {

				String exMessage = ERROR_REQUEST_INVALID + " "
						+ errorMap.get(ERROR_REQUEST_INVALID);
				throw new AdmobInvalidRequestException(exMessage);

			} else if (errorMap.containsKey(ERROR_TOKEN_INVALID)) {

				String exMessage = ERROR_TOKEN_INVALID + " " + errorMap.get(ERROR_TOKEN_INVALID);
				throw new AdmobInvalidTokenException(exMessage);
			} else {
				String code = errorMap.keySet().iterator().next();
				throw new AdmobGenericException(code + " " + errorMap.get(code));
			}
		}

	}

	private static JSONArray toJsonArray(JSONObject result, String elementName) {
		JSONArray dataObject = (result).optJSONArray(elementName);

		if (dataObject == null) {
			dataObject = new JSONArray();
			dataObject.put(result.optJSONObject(elementName));
		}
		return dataObject;
	}

	public static void syncSiteStats(String account, Context context, List<String> siteList,
			SyncCallback callback) throws AdmobRateLimitExceededException,
			AdmobAccountRemovedException, NetworkException, AdmobInvalidTokenException,
			AdmobGenericException, AdmobAskForPasswordException, AdmobInvalidRequestException {

		if (DEBUG)
			Log.d(TAG, "admob site sync request for " + siteList.size() + " sites");

		String token = authenticateAdmobAccount(account, context);

		for (int k = 0; k < siteList.size(); k++) {

			String admobSiteId = siteList.get(k);

			boolean bulkInsert = false;

			Date startDate = null;

			Calendar calendar = Calendar.getInstance();
			calendar.add(Calendar.DAY_OF_YEAR, 1);
			Date endDate = calendar.getTime();

			// read db for required sync period
			ContentAdapter contentAdapter = ContentAdapter.getInstance(AndlyticsApp.getInstance());
			List<AdmobStats> admobStats = contentAdapter.getAdmobStats(admobSiteId,
					Timeframe.LATEST_VALUE).getStats();

			if (admobStats.size() > 0) {
				// found previouse sync, no bulk import
				startDate = admobStats.get(0).getDate();

				Calendar startCal = Calendar.getInstance();
				startCal.setTime(startDate);
				startCal.add(Calendar.DAY_OF_YEAR, -4);
				startDate = startCal.getTime();

			} else {
				calendar.add(Calendar.MONTH, -6);
				startDate = calendar.getTime();
				bulkInsert = true;

			}

			List<AdmobStats> result = new ArrayList<AdmobStats>();

			//String endDate = dateFormat.format(calendar.getTime());
			JSONArray data = getData(context, account, token, "site", "stats", new String[] {
					"site_id=" + admobSiteId, "end_date=" + dateFormat.format(endDate),
					"start_date=" + dateFormat.format(startDate), "time_dimension=day",
					"order_by[date]=desc" });

			if (callback != null && bulkInsert) {
				callback.initialImportStarted();
			}

			//go through the returned array
			for (int i = 0; i < data.length(); i++) {

				try {

					//convert the line of data to a json object so we can pick specific data out of it
					JSONObject adObject = new JSONObject(data.get(i).toString());

					AdmobStats admob = new AdmobStats();
					admob.setSiteId(admobSiteId);
					admob.setClicks(adObject.getInt(KEY_CLICKS));
					admob.setCpcRevenue(Float.parseFloat(adObject.getString(KEY_CPC_REVENUE)));
					admob.setCpmRevenue(Float.parseFloat(adObject.getString(KEY_CPM_REVENUE)));
					admob.setCtr(Float.parseFloat(adObject.getString(KEY_CTR)));
					admob.setDate(dateFormat.parse(adObject.getString(KEY_DATE)));
					admob.setEcpm(Float.parseFloat(adObject.getString(KEY_ECPM)));
					admob.setExchangeDownloads(adObject.getInt(KEY_EXCHANGE_DOWNLOADS));
					admob.setFillRate(Float.parseFloat(adObject.getString(KEY_FILL_RATE)));
					admob.setHouseAdClicks(adObject.getInt(KEY_HOUSEAD_CLICKS));
					admob.setHouseadFillRate(Float.parseFloat(adObject
							.getString(KEY_HOUSEAD_FILL_RATE)));
					admob.setHouseadRequests(adObject.getInt(KEY_HOUSEAD_REQUESTS));
					admob.setImpressions(adObject.getInt(KEY_IMPRESSIONS));
					admob.setInterstitialRequests(adObject.getInt(KEY_INTERSTITIAL_REQUESTS));
					admob.setOverallFillRate(Float.parseFloat(adObject
							.getString(KEY_OVERALL_FILL_RATE)));
					admob.setRequests(adObject.getInt(KEY_REQUESTS));
					admob.setRevenue(Float.parseFloat(adObject.getString(KEY_REVENUE)));

					result.add(admob);

				} catch (Exception e) {
					throw new AdmobGenericException(e);
				}
			}

			if (bulkInsert) {
				if (result.size() > 6) {

					// insert first results single to avoid manual triggered doubles
					List<AdmobStats> subList1 = result.subList(0, 5);
					for (AdmobStats admob : subList1) {
						contentAdapter.insertOrUpdateAdmobStats(admob);
					}

					List<AdmobStats> subList2 = result.subList(5, result.size());
					contentAdapter.bulkInsertAdmobStats(subList2);

				} else {
					contentAdapter.bulkInsertAdmobStats(result);
				}
			} else {
				for (AdmobStats admob : result) {
					contentAdapter.insertOrUpdateAdmobStats(admob);
				}
			}

			if (k != siteList.size() - 1) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//Log.d(TAG, "wait to avoid rate limit exception");
			}

		}

	}

	private static String authenticateAdmobAccount(String currentAdmobAccount, Context context)
			throws AdmobRateLimitExceededException, AdmobInvalidTokenException,
			AdmobAccountRemovedException, AdmobAskForPasswordException,
			AdmobInvalidRequestException {

		String admobToken = AdmobAuthenticationUtilities.authenticateAccount(currentAdmobAccount,
				context);

		if (AdmobRequest.ERROR_RATE_LIMIT_EXCEEDED.equals(admobToken)) {
			throw new AdmobRateLimitExceededException(admobToken);
		} else if (AdmobRequest.ERROR_REQUEST_INVALID.equals(admobToken)) {
			throw new AdmobInvalidRequestException(admobToken);
		} else if (AdmobRequest.ERROR_ACCOUNT_REMOVED.equals(admobToken)) {
			throw new AdmobAccountRemovedException(admobToken, currentAdmobAccount);
		} else if (AdmobRequest.ERROR_ASK_USER_PASSWORD.equals(admobToken)) {
			throw new AdmobAskForPasswordException("Missing password");
		}

		return admobToken;
	}

	private static void invalidateAdmobToken(String accountName, String token, Context context) {
		if (DEBUG)
			Log.d(TAG, "invalidate admob token for " + accountName);
		AdmobAuthenticationUtilities.invalidateToken(token, context);
	}

	public interface SyncCallback {

		void initialImportStarted();

	}

	public static Map<String, String> getSiteList(String account, Context context)
			throws AdmobRateLimitExceededException, AdmobInvalidTokenException,
			AdmobAccountRemovedException, NetworkException, AdmobGenericException,
			AdmobAskForPasswordException, AdmobInvalidRequestException {

		Map<String, String> result = new HashMap<String, String>();

		String token = authenticateAdmobAccount(account, context);

		JSONArray data = AdmobRequest.getData(context, account, token, "site", "search",
				new String[] {});

		//go through the returned array
		for (int i = 0; i < data.length(); i++) {

			//convert the line of data to a json object so we can pick specific data out of it
			JSONObject adObject;
			try {
				adObject = new JSONObject(data.get(i).toString());

				String id = adObject.getString("id");
				String name = adObject.getString("name");
				result.put(id, name);
			} catch (JSONException e) {
				throw new AdmobGenericException(e);
			}
		}

		return result;

	}

}
