#include "Deals.hpp"
#include <string>
#include <cstdlib>
#include "PrintFile.hpp"
#include "common.hpp"
#include "ExecCommand.hpp"
#include "json.hpp"
#include "base64.h"
#include <fstream>
#include "cleanHtml.h"
#include <sstream>
#include <iostream>
#include <ctime>

using namespace std;
using json = nlohmann::json;

string url_encode(const string &value)
{
	ostringstream escaped;
	escaped.fill('0');
	escaped << hex;

	for (string::const_iterator i = value.begin(), n = value.end(); i != n; ++i)
	{
		string::value_type c = (*i);

		// Keep alphanumeric and other accepted characters intact
		if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
		{
			escaped << c;
			continue;
		}

		// Any other characters are percent-encoded
		escaped << uppercase;
		escaped << '%' << setw(2) << int((unsigned char)c);
		escaped << nouppercase;
	}

	return escaped.str();
}

int Deals::HelpCreate() const
{
	if (isRussian)
	{
		std::cout <<
			R"(
Использование: dm-cli deal create [параметры]

    Создать новую сделку

    [smhd] означает секунды, минуты, часы, дни.
    Например: --leave-before 2020-01-01, или --leave-before 10d.

    -a|--at                   Сайт проведения сделки
    -s|--seller               Продавец в сделке
    -c|--customer             Покупатель в сделке
    -t|--type [prepayment|postpayment]
                              Тип сделки
    -g|--sum                  Сумма сделки в BTC
    -l|--leave-before { <гггг-мм-дд> [чч:мм:сс UTC] | <время>[smhd] }
                              Время окончания сделки (длительность сделки), по умолчанию: 14d
    -p|--pay { <гггг-мм-дд> [чч:мм:сс UTC] | <время>[smhd] }
                              Время для оплаты сделки; по умолчанию: 1d
                                 
)"
				  << std::endl;
	}
	else
	{
		std::cout <<
			R"(
Usage: dm-cli deal create [options]

    Create a new deal

    [smhd] means seconds, minutes, hours, days.
    For example: --leave-before 2020-01-01, or --leave-before 10d.

    -a|--at                   Deal site
    -s|--seller               Seller user in deal
    -c|--customer             Customer user in deal
    -t|--type [prepayment|postpayment]
                              Deal type
    -g|--sum                  Deal sum in BTC
    -l|--leave-before { <yyyy-mm-dd> [hh:mm:ss UTC] | <time>[smhd] }
                              Deal end date (deal duration); default: 14d
    -p|--pay { <yyyy-mm-dd> [hh:mm:ss UTC] | <time>[smhd] }
                              Time for make payment; default: 1d			
   
)"
				  << std::endl;
	}
	return 0;
}
int Deals::HelpDealStatus() const
{
	if (isRussian)

	{
		std::cout <<
			R"(
Использование: dm-cli deal status { <биткоин адрес>|<код сделки> }

    Показать детали сделки

    Чтобы показать детали сделки используйте адрес оплаты сделки или код сделки.

    -i|--is-paid              Проверить является ли сделка оплаченной. Возвращает exit code 0 если сделка является оплаченной, или не ноль в других случаях.   
)"
				  << std::endl;
	}
	else
	{
		std::cout <<
			R"(
Usage: dm-cli deal status { <bitcoin_address>|<deal_code> }

    Show a deal data details

    To show a deal details use a bitcoin payment address or a deal code.

    -i|--is-paid              Check a deal is Paid. Returns an exit code 0 if deal is Paid, or non-zero otherwise.   
)"
				  << std::endl;
	}
	return 0;
}
int Deals::HelpDealComplete() const
{
	if (isRussian)
	{
		std::cout <<
			R"(
Использование: dm-cli deal complete [параметры] { <биткоин адес>|<код сделки> }

    Завершить сделку

    -p|--positive             Завершить сделку с позитивным отзывом. (действие по умолчанию)
    -r|--refund <num>         Возврат % от суммы сделки покупателю. Формат: 100.00[%]   
)"
				  << std::endl;
	}
	else
	{
		std::cout <<
			R"(
Usage: dm-cli deal complete [options] { <bitcoin_address>|<deal_code> }

    Complete a deal

    -p|--positive             Complete deal with positive feedback. (default action)
    -r|--refund <num>         Refund % number of a deal sum to the customer. Format: 100.00[%]   
)"
				  << std::endl;
	}
	return 0;
}
int Deals::HelpDealCancel() const
{
	if (isRussian)
	{
		std::cout <<
			R"(
Использование: dm-cli deal cancel [параметры] <биткоин адрес>

    Отменить сделку

    Для отмены сделки вы должны подписать адрес оплаты сделки и текст отзыва биткоин
    ключом продавца. Например, адрес, отзыв, подпись:
    2NEVoXnRgUeb3j17s7pP6DoFHBVHMb94gF5
    seller random cancel reason text
    IEOGJ5q/a2cWJPMosQmCHNN6RsXF8Xy/UgaazIoY2i5udbw/v38g1/PdWs451dvFr9Iq/Bq5YL2YdpUFTuijQ60=

    -m|--message <text>       Текст сообщения с причино отмены сделки
    -s|--signature <sig>      Биткоин подпись.   
)"
				  << std::endl;
	}
	else
	{
		std::cout <<
			R"(
Usage: dm-cli deal cancel [options] <bitcoin_address>

    Cancel a deal

    To cancel a deal you should sign the deal payment address and feeback text message
    with the seller bitcoin private key. For example, address, feedback, signature:
    2NEVoXnRgUeb3j17s7pP6DoFHBVHMb94gF5
    seller random cancel reason text
    IEOGJ5q/a2cWJPMosQmCHNN6RsXF8Xy/UgaazIoY2i5udbw/v38g1/PdWs451dvFr9Iq/Bq5YL2YdpUFTuijQ60=

    -m|--message <text>       Deal cancel reason text message
    -s|--signature <sig>      Bitcoin signature.   
)"
				  << std::endl;
	}
	return 0;
}
int Deals::HelpDealNegative() const
{
	if (isRussian)
	{
		std::cout <<
			R"(
Использование: dm-cli deal negative [options] <биткоин адрес>

    Оставить негативный отзыв

    Для оставления негативного отзыва ва должны подписать адрес оплаты сделки и текст отзыва биткоин
    ключом покупателя. Например, адрес, отзыв, подпись:
    2NEVoXnRgUeb3j17s7pP6DoFHBVHMb94gF5
    customer random feedback text
    H1u44C5cyEeKjHqnNnn09B8mIRMdqgR8PD9AeF8mv+cCEqM9HPkcWLrc8a/v2po2YmXJSeya6uy0XaLnmmmO/1o=

    -m|--message <text>       Текст сообщения отзыва
    -s|--signature <sig>      Биткоин подпись.   
)"
				  << std::endl;
	}
	else
	{
		std::cout <<
			R"(
Usage: dm-cli deal negative [options] <bitcoin_address>

    Leave a negative feedback

    To leave a negative feedback you should sign the deal payment address and feeback text message
    with the customer bitcoin private key. For example, address, feedback, signature:
    2NEVoXnRgUeb3j17s7pP6DoFHBVHMb94gF5
    customer random feedback text
    H1u44C5cyEeKjHqnNnn09B8mIRMdqgR8PD9AeF8mv+cCEqM9HPkcWLrc8a/v2po2YmXJSeya6uy0XaLnmmmO/1o=

    -m|--message <text>       Deal feedback text message
    -s|--signature <sig>      Bitcoin signature.   
)"
				  << std::endl;
	}
	return 0;
}
Deals::Deals(const InputParser &parser, const bool isRussian, const string address, bool isDebug)
{
	this->parser = parser;
	this->isRussian = isRussian;
	this->address = address;
	this->isDebug = isDebug;
}

int Deals::Process()
{

	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("create") && (this->parser.cmdOptionExists("--help") || parser.cmdOptionExists("-h")))
		return this->HelpCreate();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("status") && (this->parser.cmdOptionExists("--help") || parser.cmdOptionExists("-h")))
		return this->HelpDealStatus();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("complete") && (this->parser.cmdOptionExists("--help") || parser.cmdOptionExists("-h")))
		return this->HelpDealComplete();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("cancel") && (this->parser.cmdOptionExists("--help") || parser.cmdOptionExists("-h")))
		return this->HelpDealCancel();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("negative") && (this->parser.cmdOptionExists("--help") || parser.cmdOptionExists("-h")))
		return this->HelpDealNegative();

	return Update();
	return 0;
}

int Deals::Update()
{
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("create"))
		return Create();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("status"))
		return Status();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("complete"))
		return Complete();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("cancel"))
		return Cancel();
	if (parser.cmdOptionExists("deal") && this->parser.cmdOptionExists("negative"))
		return Negative();
	return 1;
}
string DealCreateTpl()
{
	return R"(
curl -s -X POST "{{ADDRESS}}/api/v1/deal/create" \
    -H "Content-Type: application/json" \
    -d '{"at":"{{AT}}","type":"{{TYPE}}","seller":{"address": "{{SELLER}}"},"customer":{"address":"{{CUSTOMER}}"},"payment":{"sum":"{{SUM}}" {{UNTIL}}  } {{FEETBACK}} }'   
)";
}
int Deals::Create()
{

	string at;
	string seller;
	string customer;
	string type;
	string sum;
	string leave_before;
	string pay;

	at = GetVal("-a", "--at");
	// if (at.length() == 0) at = "https://testnet-dm2.bitdeals.org";
	seller = GetVal("-s", "--seller");
	customer = GetVal("-c", "--customer");
	type = GetVal("-t", "--type");
	sum = GetVal("-g", "--sum");
	leave_before = GetVal("-l", "--leave_before");
	pay = GetVal("-p", "--pay");

	// start curl
	string data("");
	data += DealCreateTpl();

	data = Replace(data, string("{{ADDRESS}}"), address);
	data = Replace(data, string("{{AT}}"), at);
	data = Replace(data, string("{{TYPE}}"), type);
	data = Replace(data, string("{{SELLER}}"), seller);
	data = Replace(data, string("{{CUSTOMER}}"), customer);
	data = Replace(data, string("{{SUM}}"), sum);

	if (pay.length() == 0)
	{
		//{{UNTIL}}
		data = Replace(data, string("{{UNTIL}}"), "");
	}
	else
	{
		pay = GetDate(pay);
		string untl(",\"until\": \"");
		untl.append(pay);
		untl.append("\"");
		data = Replace(data, string("{{UNTIL}}"), untl);
	}

	if (leave_before.length() == 0)
	{
		//{{FEETBACK}}
		data = Replace(data, string("{{FEETBACK}}"), "");
	}
	else
	{
		leave_before = GetDate(leave_before);
		string untl(",\"feedback\": {\"leave-before\": \"");
		untl.append(leave_before);
		untl.append("\"}");

		data = Replace(data, string("{{FEETBACK}}"), untl);
	}

	string result = ExecCommand(data.c_str());
	if (result.length() == 0)
		return NoResponse("");
	nlohmann::json jsonData = nlohmann::json::parse(result);

	std::ostringstream stream;
	stream << "success: " << jsonData["result"]["success"] << endl;
	stream << "message: " << jsonData["result"]["message"] << endl;
	string payload = base64_decode(jsonData["payload"]);
	stream << payload << endl;
	string decoded = stream.str();
	cout << decoded << endl;
	if (isDebug)
		ShowDebug(data, result, 1, payload);

	return 0;
}
string Deals::GetDate(const string date)
{
	string buffer = date;
	/*
	-p|--pay { <yyyy-mm-dd> [hh:mm:ss UTC] | <time>[smhd] }
							  Time for make payment; default: 1d
	*/
	std::transform(buffer.begin(), buffer.end(), buffer.begin(), ::toupper);
	if (buffer.length() < 23)
	{
		char letter = buffer[buffer.length() - 1];
		int append = 0;
		string numval = buffer.substr(0, buffer.length() - 1);
		int num = stoi(numval);
		switch (letter)
		{
		case 'S':
			append = num;
			break;
		case 'M':
			append = num * 60;
			break;
		case 'H':
			append = num * 60 * 60;
			break;
		case 'D':
			append = num * 24 * 60 * 60;
			break;
		default:
			break;
		}
		std::time_t utcTimeInSeconds = std::time(nullptr);
		utcTimeInSeconds += append;

		tm *tmm = gmtime(&utcTimeInSeconds);
		char buff[70];
		strftime(buff, sizeof buff, "%Y-%m-%d %H:%M:%S", tmm);
		string result = buff;
		return result;
	}
	else
	{
		return buffer;
	}
}

string Deals::CommonStatus()
{
	string payaddress = parser.getLast();
	string data("curl -s -X POST \"");
	data.append(address);
	data.append("/api/v1/deal/status\" -H \"Content-Type: application/json\"  ");
	data.append("-d \"{\\\"address\\\":\\\"");
	data.append(payaddress);
	data.append("\\\"}\"");
	return data;
}
int Deals::Status()
{
	string data = CommonStatus();
	string result = ExecCommand(data.c_str());
	if (isDebug)
	{
		cout << "command line" << endl;
		cout << data << endl;
		cout << result << endl;
	}
	if (result.length() == 0)
		return NoResponse("");
	nlohmann::json jsonData = nlohmann::json::parse(result);

	std::ostringstream stream;

	bool isPayment = false;
	string payload = base64_decode(jsonData["payload"]);
	stream << payload << endl;
	// order: Paid, order: Completed, order: Executed

	if ((int)payload.find("Paid") != -1 || (int)payload.find("Completed") != -1 || (int)payload.find("Executed") != -1)
	{
		isPayment = true;
	}

	string decoded = stream.str();
	cout << decoded << endl;

	if (isDebug)
		ShowDebug(data, result, 1, decoded);
	if (parser.cmdOptionExists("-i") || parser.cmdOptionExists("--is-paid"))
	{
		return isPayment == true ? 0 : 1;
	}
	return 0;
}
int Deals::Complete()
{

	string data = CommonStatus();
	string result = ExecCommand(data.c_str());
	if (result.length() == 0)
		return NoResponse("");
	nlohmann::json jsonData = nlohmann::json::parse(result);
	nlohmann::json deal = jsonData["deal"];

	string positive = "Positive";
	int refund = 0;
	/*

	if (parser.cmdOptionExists("-n") || parser.cmdOptionExists("--negative"))
	{
		positive = "Negative";
	}

		*/
	if (parser.cmdOptionExists("--refund"))
	{
		refund = stoi(parser.getCmdOption("--refund"));
	}
	if (parser.cmdOptionExists("-r"))
	{
		refund = stoi(parser.getCmdOption("-r"));
	}
	if (refund > 0)
	{
		positive = "Negative";
	}

	deal["feedback"]["status"] = positive;
	if (refund > 0)
		deal["feedback"]["refund"] = refund;

	deal["date"] = Replace(deal["date"], " UTC", "");
	deal["feedback"]["leave_before"] = Replace(deal["feedback"]["leave_before"], " UTC", "");
	deal["payment"]["until"] = Replace(deal["payment"]["until"], " UTC", "");

	string jsf = deal.dump();
	jsf = ReplaceAll(jsf, "\"", "\\\"");
	data = "curl -s -X POST \"";
	data.append(address);
	data.append("/api/v1/deal/complete\" -H \"Content-Type: application/json\"  ");
	data.append("-d \"");
	data.append(jsf);
	data.append("\"");

	result = ExecCommand(data.c_str());
	if (result.length() == 0)
		return NoResponse("");

	std::ostringstream stream;
	stream << "success: " << jsonData["result"]["success"] << endl;
	stream << "message: " << jsonData["result"]["message"] << endl;

	string decoded = stream.str();
	std::cout << decoded << endl;

	if (isDebug)
		ShowDebug(data, decoded, 0, result);
	return 0;
}
int Deals::Cancel()
{
	string data = CommonStatus();
	string result = ExecCommand(data.c_str());
	if (result.length() == 0)
	{
		return NoResponse(data);
	}

	nlohmann::json jsonData = nlohmann::json::parse(result);
	nlohmann::json deal = jsonData["deal"];

	string message;
	string sign;
	if (parser.cmdOptionExists("-m") || parser.cmdOptionExists("--message"))
	{
		message = GetVal("-m", "--message");
	}
	if (parser.cmdOptionExists("-s") || parser.cmdOptionExists("--signature"))
	{
		sign = GetVal("-s", "--signature");
	}

	deal["seller"]["signature"] = sign;
	deal["feedback"]["comments"] = message;

	deal["date"] = Replace(deal["date"], " UTC", "");
	deal["feedback"]["leave_before"] = Replace(deal["feedback"]["leave_before"], " UTC", "");
	deal["payment"]["until"] = Replace(deal["payment"]["until"], " UTC", "");

	string jsf = deal.dump();
	jsf = ReplaceAll(jsf, "\"", "\\\"");
	data = "curl -s --location '";
	data.append(address);
	data.append("/api/v1/deal/cancel' --header 'Content-Type: application/json'  ");
	data.append("--data '");
	data.append(jsf);
	data.append("'");

	result = ExecCommand(data.c_str());
	if (result.length() == 0)
		return NoResponse("");

	std::ostringstream stream;
	stream << "success: " << jsonData["result"]["success"] << endl;
	stream << "message: " << jsonData["result"]["message"] << endl;

	string decoded = stream.str();
	std::cout << decoded << endl;

	if (isDebug)
		ShowDebug(data, decoded, 0, result);
	return 0;
}
int Deals::Negative()
{
	string data = CommonStatus();
	string result = ExecCommand(data.c_str());
	if (result.length() == 0)
		return NoResponse("");
	nlohmann::json jsonData = nlohmann::json::parse(result);
	nlohmann::json deal = jsonData["deal"];
	nlohmann::ordered_json deal2;
	string message;
	string sign;
	if (parser.cmdOptionExists("-m") || parser.cmdOptionExists("--message"))
	{
		message = GetVal("-m", "--message");
	}
	if (parser.cmdOptionExists("-s") || parser.cmdOptionExists("--signature"))
	{
		sign = GetVal("-s", "--signature");
	}

	deal2["order"] = "feedback";
	deal2["type"] = deal["type"];
	deal2["at"] = deal["at"];
	deal2["date"] = deal["date"];
	deal2["salt"] = deal["salt"];

	// deal2["seller"] = "{}";
	deal2["seller"]["address"] = deal["seller"]["address"];

	// deal2["customer"] = "{}";
	deal2["customer"]["address"] = deal["customer"]["address"];
	deal2["customer"]["signature"] = sign;

	deal2["payment"]["address"] = deal["payment"]["address"];
	deal2["payment"]["until"] = deal["payment"]["until"];
	deal2["payment"]["sum"] = deal["payment"]["sum"];

	// deal2["feedback"] = "{}";
	deal2["feedback"]["leave_before"] = deal["feedback"]["leave_before"];
	deal2["feedback"]["comments"] = message;

	string jsf = deal2.dump();
	jsf = ReplaceAll(jsf, "leave_before", "leave-before");
	data = "curl -s  --location '";
	data.append(address);
	data.append("/api/v1/deal/feedback'");
	data.append(" --header 'Content-Type: application/json'  ");
	data.append("--data '");
	data.append(jsf);
	data.append("'");

	result = ExecCommand(data.c_str());
	if (result.length() == 0)
		return NoResponse("");

	std::ostringstream stream;
	stream << "success: " << jsonData["result"]["success"] << endl;
	stream << "message: " << jsonData["result"]["message"] << endl;

	string decoded = stream.str();
	std::cout << decoded << endl;

	if (isDebug)
		ShowDebug(data, decoded, 0, result);
	return 0;
}

string Deals::GetVal(const string first, const string second)
{
	string aval;
	string res("");
	if (parser.cmdOptionExists(first))
	{
		aval = parser.getCmdOption(first);
		if (aval.length() > 0)
			res = aval;
	}
	if (parser.cmdOptionExists(second))
	{
		aval = parser.getCmdOption(second);
		if (aval.length() > 0)
			res = aval;
	}
	return res;
}
