#include "pch.h"
#include "http/HttpRequest.h"
#include "http/HttpRequestUtil.h"
#include "twitchsdk/core/json/reader.h"
#include "Internal.h"

namespace json = ttv::json;
using namespace TwitchInGames;

namespace {
	tstring const defaultProfileImageUrl = _T("https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_300x300.png");
	tstring const helixUsersRootUrl = _T("https://api.twitch.tv/helix/users");
}

User::User() : viewCount(0) {}

User::~User() {}

User User::ParseResponseForUser(std::vector<char> const& response) {
	// Parse the response as JSON.
	json::Value jsonVal;
	json::Reader jsonReader;
	bool parseSucceeded = jsonReader.parse(response.data(), response.data() + response.size(), jsonVal);
	if(!parseSucceeded) {
		DebugWriteLine(_T("[User::ParseResponseForUser] JSON parsing failed"));
		throw TwitchException(FromPlatformError(ERROR_INVALID_RESPONSE));
	}

	// Get references for all of the user fields.
	auto const& jUserInfo = jsonVal["data"][static_cast<size_t>(0)];
	auto const& jBroadcasterType = jUserInfo["broadcaster_type"];
	auto const& jDescription = jUserInfo["description"];
	auto const& jDisplayName = jUserInfo["display_name"];
	auto const& jEmail = jUserInfo["email"];
	auto const& jId = jUserInfo["id"];
	auto const& jLogin = jUserInfo["login"];
	auto const& jOfflineImageUrl = jUserInfo["offline_image_url"];
	auto const& jProfileImageUrl = jUserInfo["profile_image_url"];
	auto const& jType = jUserInfo["type"];
	auto const& jViewCount = jUserInfo["view_count"];

	// Populate and return a User instance.
	User user;
	user.login = ToTstring(jLogin.asString());
	user.displayName = ToTstring(jDisplayName.asString());
	if(jBroadcasterType.isConvertibleTo(json::stringValue)) {
		user.broadcasterType = ToTstring(jBroadcasterType.asString());
	}
	if(jDescription.isConvertibleTo(json::stringValue)) {
		user.description = ToTstring(jDescription.asString());
	}
	if(jEmail.isConvertibleTo(json::stringValue)) {
		user.email = ToTstring(jEmail.asString());
	}
	if(jOfflineImageUrl.isConvertibleTo(json::stringValue)) {
		user.offlineImageUrl = ToTstring(jOfflineImageUrl.asString());
	}
	if(jProfileImageUrl.isConvertibleTo(json::stringValue)) {
		user.profileImageUrl = ToTstring(jProfileImageUrl.asString());
	}
	if(jType.isConvertibleTo(json::stringValue)) {
		user.type = ToTstring(jType.asString());
	}
	if(jViewCount.isInt()) {
		user.viewCount = static_cast<unsigned>(jViewCount.asInt());
	}
	if(jId.isConvertibleTo(json::stringValue)) {
		user.id = ToTstring(jId.asString());
	}
	return user;
}

std::future<User> User::FetchCurrent(tstring const& clientId, tstring const& token) {
	auto fn = [token, clientId]() {
		// Fetch and parse the Helix user.
		auto httpResponse = HttpRequest(clientId, token).Get(helixUsersRootUrl);
		ThrowIfResponseFailed(httpResponse);
		auto user = ParseResponseForUser(httpResponse.Response);

		// Send a science event.
		SendUserScienceEvent(clientId, user.Id, _T("sdk_user_get"), {});

		return user;
	};
	return std::async(std::launch::deferred, fn);
}

std::future<User> User::FetchOtherByLogin(tstring const& clientId, tstring const& login) {
	return FetchOther(clientId, _T("?login="), login);
}

std::future<User> User::FetchOtherById(tstring const& clientId, tstring const& id) {
	return FetchOther(clientId, _T("?id="), id);
}

std::future<User> User::FetchOther(tstring const& clientId, string_t query, tstring const& value) {
	auto fn = [clientId, query = tstring(query), value]() {
		// Fetch and parse the Helix user.
		tstring url = helixUsersRootUrl + query + value;
		auto httpResponse = HttpRequest(clientId).Get(url);
		ThrowIfResponseFailed(httpResponse);
		auto user = ParseResponseForUser(httpResponse.Response);

		// Send a science event.
		auto key = query[1] == _T('l') ? _T("login") : _T("user_id");
		SendUserScienceEvent(clientId, tstring(), _T("sdk_user_get"), { { key, value } });

		return user;
	};
	return std::async(std::launch::deferred, fn);
}

static std::future<std::vector<char>> FetchImage(tstring const& imageUrl) {
	auto fn = [imageUrl]() {
		auto const& url = imageUrl.empty() ? defaultProfileImageUrl : imageUrl;
		auto httpResponse = TwitchInGames::HttpRequest().Get(url);
		ThrowIfResponseFailed(httpResponse);
		return httpResponse.Response;
	};
	return std::async(std::launch::deferred, fn);
}

std::future<std::vector<char>> User::FetchOfflineImage() {
	return FetchImage(offlineImageUrl);
}

std::future<std::vector<char>> User::FetchProfileImage() {
	return FetchImage(profileImageUrl);
}

std::future<void> User::UpdateDescription(tstring const& clientId, tstring const& token, tstring const& newDescription) {
	auto fn = [clientId, token, userId = Id, newDescription]() {
		tstring url = helixUsersRootUrl + _T("?description=") + UrlEncode(newDescription);
		auto httpResponse = HttpRequest(clientId, token).Put(url);
		ThrowIfResponseFailed(httpResponse);

		// Send a science event.
		auto descriptionSize = to_tstring(newDescription.size());
		SendUserScienceEvent(clientId, userId, _T("sdk_user_update"), { { _T("description_size"), descriptionSize } });
	};
	return std::async(std::launch::deferred, fn);
}
