#include <misc/endianness.h>
#include <misc/base64.h>
#include <misc/hex.h>
#include <log/LogUtils.h>
#include <LicenseManager.pb.h>
#include <shared/License.h>
#include "LicenseRequest.pb.h"
#include "shared/License.h"
#include "LicenseServer.h"
#include "WebAPI.h"
#include "StatisticManager.h"
#include "UserManager.h"

using namespace std;
using namespace std::chrono;
using namespace license;
using namespace ts;


inline void generate(char* buffer, size_t length){
	for(int index = 0; index < length; index++)
		buffer[index] = rand();
}

#define TEST_PROTOCOL_STATE(expected)               \
if(client->protocol.state != protocol::expected) {  \
	error = "invalid protocol state";               \
	return false;                                   \
}

#define ENSURE_PACKET_SIZE(expected)                \
if(packet.data.length() < (expected)) {             \
	error = "too small packet!";                    \
	return false;                                   \
}

#define PARSE_PROTO(class, var)                     \
ts::proto::license::class var;                      \
if(!var.ParseFromString(packet.data)) {             \
	error = "invalid data!";                        \
	return false;                                   \
}

#define CRYPT_KEY_LENGTH 32
bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protocol::packet& packet, std::string &error) {
	TEST_PROTOCOL_STATE(HANDSCAKE);
	ENSURE_PACKET_SIZE(4);
	if((uint8_t) packet.data[0] != 0xC0 || (uint8_t) packet.data[1] != 0xFF || (uint8_t) packet.data[2] != 0xEE) {
		error = "invalid magic!";
		return false;
	}
	if((uint8_t) packet.data[3] != LICENSE_PROT_VERSION) {
		error = "invalid version!";
		return false;
	}

	bool manager = false;
	if(packet.data.length() >= 4 && (uint8_t) packet.data[4] == 1) {
		manager = true;
		//Its a manager!
	}

	char buffer_cryptkey[CRYPT_KEY_LENGTH];
	generate(buffer_cryptkey, CRYPT_KEY_LENGTH);

	uint8_t buffer[128];
	size_t buffer_index = 0;
	buffer[buffer_index++] = 0xAF;
	buffer[buffer_index++] = 0xFE;
	buffer[buffer_index++] = LICENSE_PROT_VERSION;
	le2be16(CRYPT_KEY_LENGTH, buffer, buffer_index, &buffer_index);
	memcpy(&buffer[buffer_index], buffer_cryptkey, CRYPT_KEY_LENGTH);
	buffer_index += CRYPT_KEY_LENGTH;
	if(manager)
		buffer[buffer_index++] = 0x01; //Manager accepted

	client->sendPacket({protocol::PACKET_SERVER_HANDSHAKE, string((char*) buffer, buffer_index)});
	client->protocol.cryptKey = string(buffer_cryptkey, 32);

	if(manager) {
		client->protocol.state = protocol::MANAGER_AUTHORIZATION;
		client->type = ClientType::MANAGER;
	} else {
		client->protocol.state = protocol::SERVER_VALIDATION;
		client->type = ClientType::SERVER;
	}

	return true;
}

bool LicenseServer::handleDisconnect(shared_ptr<ConnectedClient>& client, protocol::packet& packet, std::string &error) {
	logMessage("[CLIENT][" + client->address()  + "] Remote disconnect. Reason: " + packet.data);
	this->closeConnection(client);
	return true;
}

inline void fill_info(proto::license::LicenseInfo* proto, const shared_ptr<LicenseInfo>& info, const std::string& key) {
    proto->set_key(key);
    if(info) {
	    proto->set_username(info->username);
	    proto->set_first_name(info->first_name);
	    proto->set_last_name(info->last_name);
	    proto->set_email(info->email);
	    proto->set_type(info->type);
	    proto->set_created(duration_cast<milliseconds>(info->creation.time_since_epoch()).count());
	    proto->set_begin(duration_cast<milliseconds>(info->start.time_since_epoch()).count());
	    proto->set_end(duration_cast<milliseconds>(info->end.time_since_epoch()).count());
    } else {
	    proto->set_username("invalid (null)");
	    proto->set_first_name("invalid (null)");
	    proto->set_last_name("invalid (null)");
	    proto->set_email("invalid (null)");
	    proto->set_type(0);
	    proto->set_created(0);
	    proto->set_begin(0);
	    proto->set_end(0);
    }
}

std::string string_to_hex(const std::string& input)
{
	static const char* const lut = "0123456789ABCDEF";
	size_t len = input.length();

	std::string output;
	output.reserve(2 * len);
	for (size_t i = 0; i < len; ++i)
	{
		const unsigned char c = input[i];
		output.push_back(lut[c >> 4]);
		output.push_back(lut[c & 15]);
	}
	return output;
}

bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
	TEST_PROTOCOL_STATE(SERVER_VALIDATION);
	PARSE_PROTO(ServerValidation, pkt);

	shared_ptr<License> remoteLicense = nullptr;
	if(pkt.licensed() && !pkt.has_license()) {
		//TODO shutdown server
	}
	if(!pkt.has_info()) {
		error = "invalid data or missing data";
		return false;
	}
	if(pkt.has_license()){ //Client has license
		remoteLicense = readLocalLicence(pkt.license(), error);
		if(!remoteLicense) {
			error = "Could not read remote key: " + error;
			return false;
		};
		logMessage(LOG_GENERAL, "[CLIENT][{}] Got remote license. Registered to {}. Key: {} (0x{})", client->address(), remoteLicense->owner(), base64::encode(remoteLicense->key()), string_to_hex(remoteLicense->key()));
		client->key = remoteLicense->key();
	} else { }
    if(pkt.licensed() && !pkt.has_license_info()) {
	    error = "Invalid content!";
	    return false;
	}

    logMessage(LOG_GENERAL, "[CLIENT][{}] Got some server information. TeaSpeak-Version: {} uname: {}", client->address(), pkt.info().version(), pkt.info().uname());
	ts::proto::license::LicenseResponse response;

	//Forces
	client->unique_identifier = pkt.info().has_unique_id() ? pkt.info().unique_id() : client->address();
	if(remoteLicense && pkt.licensed()) {
        auto info = this->manager->licenseInfo(remoteLicense->key());

        if(!info) {
	        response.mutable_blacklist()->set_reason("License hasn't been found.");
	        response.set_valid(false);

	        /*
            logMessage(LOG_GENERAL, "[CLIENT][{}] Unknown license! Adding it to database!", client->address());
            auto db_info = make_shared<LicenseInfo>();
            db_info->start = system_clock::now();
            db_info->end = remoteLicense->end();
            db_info->last_name = "unknown";
            db_info->first_name = "unknown";
            db_info->username = remoteLicense->owner();
            db_info->email = "unknonw@unknown";
            db_info->creation = system_clock::now();
            db_info->type = remoteLicense->data.type;
            this->manager->registerLicense(remoteLicense->key(), db_info, "teaforo-fix");
            info = this->manager->licenseInfo(remoteLicense->key());
            if(!info) {
                error = "could not insert key!";
                return false;
            }
            */

	        logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license hasn't been found in database. Shutting down server!", client->address());
        } else {
	        if(info->deleted) {
		        response.mutable_blacklist()->set_reason("License has been deleted.");
		        response.set_valid(false);
		        logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license has been deleted! Shutting down server!", client->address());
	        } else {
		        fill_info(response.mutable_license_info(), info, remoteLicense->data.licenceKey);
		        response.set_valid(info->isValid());
	        }
        }
		this->manager->logRequest(remoteLicense->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
	} else {
	    response.set_valid(true);
	}

	if(response.valid())
		response.mutable_blacklist()->set_state(ts::proto::license::VALID);
	else {
		if(!response.has_license_info())
			fill_info(response.mutable_license_info(), nullptr, ""); /* "Hack" for old clients which require a license. Else the server would not be stopped */
		response.mutable_blacklist()->set_state(ts::proto::license::BLACKLISTED); /* "Hack" for all old clients */
		client->invalid_license = true;
	}

	client->sendPacket(protocol::packet{protocol::PACKET_SERVER_VALIDATION_RESPONSE, response});
	client->protocol.state = protocol::PROPERTY_ADJUSTMENT;
	return true;
}

bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
	TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT);
	if(client->invalid_license) {
		ts::proto::license::PropertyUpdateResponse response;
		response.set_accepted(true);
		response.set_reset_speach(false);
		response.set_speach_total_remote(0);
		response.set_speach_varianz_corrector(0);
		client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response});
		this->disconnectClient(client, "finished");

		/* Not sure if we really want here to log these data. May put a lvalid=false within the db? */
		return true;
	}
	PARSE_PROTO(PropertyUpdateRequest, pkt);

	logMessage("[CLIENT][" + client->address() + "] Got server statistics:");
	logMessage("[CLIENT][" + client->address() + "]   Spoken total       : " + to_string(pkt.speach_total()));
	logMessage("[CLIENT][" + client->address() + "]   Spoken dead        : " + to_string(pkt.speach_dead()));
	logMessage("[CLIENT][" + client->address() + "]   Spoken online      : " + to_string(pkt.speach_online()));
	logMessage("[CLIENT][" + client->address() + "]   Spoken varianz     : " + to_string(pkt.speach_varianz()));
	logMessage("[CLIENT][" + client->address() + "]   -------------------------------");
	logMessage("[CLIENT][" + client->address() + "]   Users online       : " + to_string(pkt.clients_online()));
	logMessage("[CLIENT][" + client->address() + "]   Web Users online   : " + to_string(pkt.web_clients_online()));
	logMessage("[CLIENT][" + client->address() + "]   Queries online     : " + to_string(pkt.queries_online()));
	logMessage("[CLIENT][" + client->address() + "]   Bots online        : " + to_string(pkt.bots_online()));
	logMessage("[CLIENT][" + client->address() + "]   Servers            : " + to_string(pkt.servers_online()));
	this->manager->logStatistic(client->key, client->unique_identifier, client->address(), pkt);
	//TODO test stuff if its possible!

	ts::proto::license::WebCertificate* web_certificate{nullptr};
	if(pkt.has_web_cert_revision()) {
		logMessage("[CLIENT][" + client->address() + "]   -------------------------------");
		logMessage("[CLIENT][" + client->address() + "]   Web cert revision : " + hex::hex(pkt.web_cert_revision()));

		auto cert = this->web_certificate;
		if(cert && cert->revision != pkt.web_cert_revision()) {
			web_certificate = new ts::proto::license::WebCertificate{};
			web_certificate->set_key(cert->key);
			web_certificate->set_certificate(cert->certificate);
			web_certificate->set_revision(cert->revision);
		}
	}

	ts::proto::license::PropertyUpdateResponse response;
	response.set_accepted(true);
    response.set_reset_speach(pkt.speach_total() < 0);
	response.set_speach_total_remote(pkt.speach_total());
	response.set_speach_varianz_corrector(0);
	response.set_allocated_web_certificate(web_certificate);
	client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response});
	this->disconnectClient(client, "finished");

	if(this->statistics)
		this->statistics->reset_cache_general();

	if(this->web_statistics)
		this->web_statistics->async_broadcast_notify_general_update();
	return true;
}

bool LicenseServer::handlePacketAuth(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
	TEST_PROTOCOL_STATE(MANAGER_AUTHORIZATION);
	PARSE_PROTO(AuthorizationRequest, pkt);

	logMessage("[MANAGER][" + client->address() + "] Got login. User: " + pkt.username() + " Password: " + pkt.password());

	ts::proto::license::AuthorizationResponse response;
	response.set_success(false);
    if(this->user_manager) {
	    auto user_account = this->user_manager->find_user(pkt.username());
	    if(user_account) {
		    if(user_account->verify_password(pkt.password())) {
		    	switch(user_account->status()) {
		    		case User::Status::ACTIVE:
					    response.set_success(true);
					    break;
		    		case User::Status::BANNED:
					    response.set_message("you have been banned");
					    break;
				    case User::Status::DISABLED:
					    response.set_message("you have been disabled");
					    break;
		    		default:
					    response.set_message("Your account hasn't been activated");
					    break;
		    	}
		    }
	    }
    } else {
	    response.set_success(pkt.password() == "HelloWorld");
    }
    if(!response.has_message() && !response.success())
		response.set_message("username or password mismatch");

    if(response.success()) {
	    logMessage("[MANAGER][" + client->address() + "] Got succeeded user login. User: " + pkt.username() + " Password: " + pkt.password());
	    client->username = pkt.username();
	    client->protocol.state = protocol::MANAGER_CONNECTED;
    } else
	    logMessage("[MANAGER][" + client->address() + "] Got failed user login. User: " + pkt.username() + " Password: " + pkt.password());

	client->sendPacket(protocol::packet{protocol::PACKET_SERVER_AUTH_RESPONSE, response});
	return true;
}

bool LicenseServer::handlePacketLicenseCreate(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
	TEST_PROTOCOL_STATE(MANAGER_CONNECTED);
	PARSE_PROTO(LicenseCreateRequest, pkt);

    logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Register new license to {} {} ({}). E-Mail: {}", pkt.issuer_first_name(), pkt.issuer_last_name(), pkt.issuer_username(), pkt.issuer_email());

    ts::proto::license::LicenseCreateResponse response;

    auto db_info = make_shared<LicenseInfo>();
    db_info->start = system_clock::time_point() + milliseconds(pkt.begin());
    db_info->end = system_clock::time_point() + milliseconds(pkt.end());
    db_info->last_name = pkt.issuer_last_name();
    db_info->first_name = pkt.issuer_first_name();
    db_info->username = pkt.issuer_username();
    db_info->email = pkt.issuer_email();
    db_info->creation = system_clock::now();
    db_info->type = static_cast<LicenseType>(pkt.type());

	auto license = license::createLocalLicence(db_info->type, db_info->end, db_info->first_name + db_info->last_name);
	auto parsed_license = license::readLocalLicence(license, error);
	if(!parsed_license) {
		response.set_error("failed to register license (parse)");
	} else {

		if(!this->manager->registerLicense(parsed_license->key(), db_info, client->username)) {
			response.set_error("failed to register license");
		} else {
			fill_info(response.mutable_license(), db_info, parsed_license->key());
			response.set_exported_key(license);
		}
	}
    client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LICENSE_CREATE_RESPONSE, response});
	return true;
}

bool LicenseServer::handlePacketLicenseList(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
	TEST_PROTOCOL_STATE(MANAGER_CONNECTED);
	PARSE_PROTO(LicenseListRequest, pkt);

	proto::license::LicenseListResponse response;
    response.set_end(false);

	for(const auto& info : this->manager->listLicenses(pkt.offset(), pkt.count())) {
		auto entry = response.add_entries();
		fill_info(entry, info.second, info.first);
	}
	client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LIST_RESPONSE, response});

	return true;
}

bool LicenseServer::handlePacketLicenseDelete(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
	TEST_PROTOCOL_STATE(MANAGER_CONNECTED);
	PARSE_PROTO(LicenseDeleteRequest, pkt);

	proto::license::LicenseDeleteResponse response;
	response.set_succeed(this->manager->deleteLicense(pkt.key(), pkt.full()));
	client->sendPacket(protocol::packet{protocol::PACKET_CLIENT_DELETE_RESPONSE, response});

	return true;
}