package jp.cyrus.tiktok4j.impl;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.fasterxml.jackson.databind.JsonNode;

import jp.cyrus.tiktok4j.TikTok4j;
import jp.cyrus.tiktok4j.models.Device;
import jp.cyrus.tiktok4j.models.Feed;
import jp.cyrus.tiktok4j.signingServices.SigningService;
import jp.cyrus.tiktok4j.utils.JsonHelper;
import jp.cyrus.tiktok4j.utils.LogHelper;
import jp.cyrus.tiktok4j.utils.TikTokLogicHelper;
import jp.cyrus.tiktok4j.utils.UrlHelper;

/**
 * Official TikTok App based implementation.
 *
 * @author cyrus
 */
public class TikTok4jTikTokImpl extends TikTok4j {

	/**
	 * API BASE URL.
	 */
	private static final String API_BASE_URL = "https://api2.musical.ly";

	/**
	 * USER AGENT.
	 */
	private static final String USER_AGENT = "com.zhiliaoapp.musically/2018052132 (Linux; U; Android 8.0.0; en_US; Pixel 2; Build/ABCXYZ; Cronet/58.0.2991.0)";

	/**
	 * SigningService.
	 */
	private SigningService signingService;

	/**
	 * Device.
	 */
	private Device device;

	/**
	 * Constructor.
	 *
	 * @param signingService
	 */
	public TikTok4jTikTokImpl(SigningService signingService) {
		this.signingService = signingService;
	}

	@Override
	public Device deviceRegister() {
		if (device == null) {
			// Create dummy data
			device = new DummyTikTokImpl().deviceRegister();
		}
		return device;
	}

	@Override
	public JsonNode loginWithEmail(String email, String password) {
		try {
			// Get device info
			Device device = deviceRegister();

			// Create request paramater
			Map<String, Object> queryParameterMap = getDefaultQueryString();

			// Get URL
			String url = API_BASE_URL + "/passport/user/login";
			String urlWithParameter = UrlHelper.addQueryParameterToUrl(url, queryParameterMap);
			System.out.println("url = " + urlWithParameter);

			// Get signed URL
			String signedUrl = signingService.signUrl(
					urlWithParameter, TikTokLogicHelper.getServerTime(), device.deviceId);
			System.out.println("signedUrl = " + signedUrl);

			// Create request paramater
			ArrayList<BasicNameValuePair> requestData = new ArrayList<>();
			requestData.add(new BasicNameValuePair("mix_mode", "1"));
			requestData.add(new BasicNameValuePair("username", ""));
			requestData.add(new BasicNameValuePair("email", TikTokLogicHelper.getEncryptedString(email)));
			requestData.add(new BasicNameValuePair("mobile", ""));
			requestData.add(new BasicNameValuePair("account", ""));
			requestData.add(new BasicNameValuePair("password", TikTokLogicHelper.getEncryptedString(password)));
			requestData.add(new BasicNameValuePair("multi_login", "1"));
			requestData.add(new BasicNameValuePair("captcha", ""));

			// Set request paramater
			HttpPost httpPost = new HttpPost(signedUrl);
			httpPost.setHeader("Cookie", String.format("Cookie: install_id=%s", device.installId));
			httpPost.setHeader("User-Agent", USER_AGENT);
			httpPost.setEntity(new UrlEncodedFormEntity(requestData, StandardCharsets.UTF_8));

			// Execute request
			try (CloseableHttpClient client = HttpClientBuilder.create().build();
					CloseableHttpResponse response = client.execute(httpPost)) {
				System.out.println(response);

				// Print HTTP status code
				System.out.println("code = " + response.getStatusLine().getStatusCode());

				// Print response
				String responseString = EntityUtils.toString(response.getEntity());
				System.out.println("response = " + responseString);

				// Get response JSON
				return JsonHelper.getObjectMapper().readTree(responseString);
			}
		} catch (Exception e) {
			e.printStackTrace();

			// FIXME
			return null;
		}
	}

	@Override
	public Feed feed() {
		// Get device info
		Device device = deviceRegister();

		String url = API_BASE_URL + "/aweme/v1/feed";
		Map<String, Object> queryParameterMap = new LinkedHashMap<>();
		queryParameterMap.put("count", 20);
		queryParameterMap.put("offset", 0);
		queryParameterMap.put("max_cursor", 0);
		queryParameterMap.put("type", 0);
		queryParameterMap.put("is_cold_start", 1);
		queryParameterMap.put("pull_type", 1);
		queryParameterMap.putAll(getDefaultQueryString());

		// Get URL
		String urlWithParameter = UrlHelper.addQueryParameterToUrl(url, queryParameterMap);

		// Get signed URL
		String signedUrl = signingService.signUrl(
				urlWithParameter, TikTokLogicHelper.getServerTime(), device.deviceId);

		return getHttpResponseAsValue(signedUrl, Feed.class);
	}

	/**
	 * Get default query string.
	 *
	 * @return
	 */
	private Map<String, Object> getDefaultQueryString() {
		// Get device info
		Device device = deviceRegister();

		Map<String, Object> queryParameterMap = new LinkedHashMap<>();
		queryParameterMap.put("ts", TikTokLogicHelper.getServerTime());
		queryParameterMap.put("js_sdk_version", "");
		queryParameterMap.put("app_type", "normal");
		queryParameterMap.put("region", "JP");
		queryParameterMap.put("version_name", "5.8.3");
		queryParameterMap.put("timezone_name", "Asia%2FTokyo");
		queryParameterMap.put("device_type", "LGL22");
		queryParameterMap.put("uild_number", "5.8.3");
		queryParameterMap.put("resolution", "1080*1776");
		queryParameterMap.put("aid", "1180");
		queryParameterMap.put("app_name", "trill");
		queryParameterMap.put("_rticket", System.currentTimeMillis());
		queryParameterMap.put("device_patform", "android");
		queryParameterMap.put("version_code", "583");
		queryParameterMap.put("uoo", "0");
		queryParameterMap.put("dpi", "480");
		queryParameterMap.put("sys_region", "JP");
		queryParameterMap.put("ssmix", "a");
		queryParameterMap.put("s_api", "19");
		queryParameterMap.put("timezone_offset", "32400");
		queryParameterMap.put("pass-route", "1");
		queryParameterMap.put("device_brand", "KDDI");
		queryParameterMap.put("maifest_version_code", "583");
		queryParameterMap.put("os_version", "4.4.2");
		queryParameterMap.put("ab_version", "5.8.3");
		queryParameterMap.put("update_version_code", "5830");
		queryParameterMap.put("app_language", "ja");
		queryParameterMap.put("ac", "wifi");
		queryParameterMap.put("pass-region", "1");
		queryParameterMap.put("language", "ja");
		queryParameterMap.put("is_my_cn", "0");
		queryParameterMap.put("channel", "googleplay");
		queryParameterMap.put("iid", device.installId);
		queryParameterMap.put("openudid", TikTokLogicHelper.getOpenudid());
		queryParameterMap.put("device_id", device.deviceId);
		return queryParameterMap;
	}

	/**
	 * Get HTTP response.
	 *
	 * @param url
	 * @return
	 */
	private static JsonNode getHttpResponseAsJson(String url) {
		try {
			LogHelper.debug("Get : " + url);
			HttpGet httpGet = new HttpGet(url);
			httpGet.setHeader("Host", UrlHelper.getHostname(url));
			httpGet.setHeader("X-SS-TC", "0");
			httpGet.setHeader("User-Agent", USER_AGENT);
			httpGet.setHeader("Accept-Encoding", "gzip");
			httpGet.setHeader("Connection", "keep-alive");
			httpGet.setHeader("X-Tt-Token", "");
			httpGet.setHeader("sdk-version", "1");
			httpGet.setHeader("Cookie", "");

			try (CloseableHttpClient client = HttpClientBuilder.create().build();
					CloseableHttpResponse response = client.execute(httpGet)) {
				LogHelper.debug(response);
				LogHelper.debug("code = " + response.getStatusLine().getStatusCode());
				String responseString = EntityUtils.toString(response.getEntity());
				LogHelper.debug("response = " + responseString);
				return JsonHelper.getObjectMapper().readTree(responseString);
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Get HTTP response.
	 *
	 * @param url
	 * @param valueType
	 * @return
	 */
	private static <T> T getHttpResponseAsValue(String url, Class<T> valueType) {
		try {
			return JsonHelper.getObjectMapper().readValue(getHttpResponseAsJson(url).toString(), valueType);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}