#include "chaintopology.h"
#include "failure.h"
#include "jsonrpc.h"
#include "lightningd.h"
#include "log.h"
#include "onion.h"
#include "pay.h"
#include "peer.h"
#include "routing.h"
#include <ccan/str/hex/hex.h>
#include <ccan/structeq/structeq.h>
#include <inttypes.h>

/* Outstanding "pay" commands. */
struct pay_command {
	struct list_node list;
	struct sha256 rhash;
	u64 msatoshis;
	struct pubkey id;
	/* Set if this is in progress. */
	struct htlc *htlc;
	/* Preimage if this succeeded. */
	struct rval *rval;
	struct command *cmd;
};

static void json_pay_success(struct command *cmd, const struct rval *rval)
{
	struct json_result *response;

	response = new_json_result(cmd);
	json_object_start(response, NULL);
	json_add_hex(response, "preimage", rval, sizeof(*rval));
	json_object_end(response);
	command_success(cmd, response);
}

static void handle_json(struct command *cmd, const struct htlc *htlc)
{
	FailInfo *f;
	struct pubkey id;
	const char *idstr = "INVALID";

	if (htlc->r) {
		json_pay_success(cmd, htlc->r);
		return;
	}

	f = failinfo_unwrap(cmd, htlc->fail, tal_count(htlc->fail));
	if (!f) {
		command_fail(cmd, "failed (bad message)");
		return;
	}

	if (proto_to_pubkey(cmd->dstate->secpctx, f->id, &id))
		idstr = pubkey_to_hexstr(cmd, cmd->dstate->secpctx, &id);

	command_fail(cmd,
		     "failed: error code %u node %s reason %s",
		     f->error_code, idstr, f->reason ? f->reason : "unknown");
}

void complete_pay_command(struct lightningd_state *dstate,
			  const struct htlc *htlc)
{
	struct pay_command *i;

	list_for_each(&dstate->pay_commands, i, list) {
		if (i->htlc == htlc) {
			if (htlc->r)
				i->rval = tal_dup(i, struct rval, htlc->r);
			i->htlc = NULL;

			/* Can be NULL if JSON RPC goes away. */
			if (i->cmd)
				handle_json(i->cmd, htlc);
			return;
		}
	}

	/* Can happen if RPC connection goes away. */
	log_unusual(dstate->base_log, "No command for HTLC %"PRIu64" %s",
		    htlc->id, htlc->r ? "fulfill" : "fail");
}

/* When JSON RPC goes away, cmd is freed: detach from any running paycommand */
static void remove_cmd_from_pc(struct command *cmd)
{
	struct pay_command *pc;

	list_for_each(&cmd->dstate->pay_commands, pc, list) {
		if (pc->cmd == cmd) {
			pc->cmd = NULL;
			return;
		}
	}
	/* We can reach here, in the case where another pay command
	 * re-uses the pc->cmd before we get around to cleaning up. */
}

static struct pay_command *find_pay_command(struct lightningd_state *dstate,
					    const struct sha256 *rhash)
{
	struct pay_command *pc;

	list_for_each(&dstate->pay_commands, pc, list) {
		if (structeq(rhash, &pc->rhash))
			return pc;
	}
	return NULL;
}

static void json_add_route(struct json_result *response,
			   secp256k1_context *secpctx,
			   const struct pubkey *id,
			   u64 amount, unsigned int delay)
{
	json_object_start(response, NULL);
	json_add_pubkey(response, secpctx, "id", id);
	json_add_u64(response, "msatoshis", amount);
	json_add_num(response, "delay", delay);
	json_object_end(response);
}

static void json_getroute(struct command *cmd,
			  const char *buffer, const jsmntok_t *params)
{
	struct pubkey id;
	jsmntok_t *idtok, *msatoshistok;
	struct json_result *response;
	int i;
	u64 msatoshis;
	s64 fee;
	struct node_connection **route;
	struct peer *peer;
	u64 *amounts, total_amount;
	unsigned int total_delay, *delays;

	if (!json_get_params(buffer, params,
			     "id", &idtok,
			     "msatoshis", &msatoshistok,
			     NULL)) {
		command_fail(cmd, "Need id and msatoshis");
		return;
	}

	if (!pubkey_from_hexstr(cmd->dstate->secpctx,
				buffer + idtok->start,
				idtok->end - idtok->start, &id)) {
		command_fail(cmd, "Invalid id");
		return;
	}

	if (!json_tok_u64(buffer, msatoshistok, &msatoshis)) {
		command_fail(cmd, "'%.*s' is not a valid number",
			     (int)(msatoshistok->end - msatoshistok->start),
			     buffer + msatoshistok->start);
		return;
	}

	peer = find_route(cmd->dstate, &id, msatoshis, &fee, &route);
	if (!peer) {
		command_fail(cmd, "no route found");
		return;
	}

	/* Fees, delays need to be calculated backwards along route. */
	amounts = tal_arr(cmd, u64, tal_count(route)+1);
	delays = tal_arr(cmd, unsigned int, tal_count(route)+1);
	total_amount = msatoshis;

	total_delay = 0;
	for (i = tal_count(route) - 1; i >= 0; i--) {
		amounts[i+1] = total_amount;
		total_amount += connection_fee(route[i], total_amount);

		total_delay += route[i]->delay;
		if (total_delay < route[i]->min_blocks)
			total_delay = route[i]->min_blocks;
		delays[i+1] = total_delay;
	}
	/* We don't charge ourselves any fees. */
	amounts[0] = total_amount;
	/* We do require delay though. */
	total_delay += peer->nc->delay;
	if (total_delay < peer->nc->min_blocks)
		total_delay = peer->nc->min_blocks;
	delays[0] = total_delay;

	response = new_json_result(cmd);
	json_object_start(response, NULL);
	json_array_start(response, "route");
	json_add_route(response, cmd->dstate->secpctx,
		       peer->id, amounts[0], delays[0]);
	for (i = 0; i < tal_count(route); i++)
		json_add_route(response, cmd->dstate->secpctx,
			       &route[i]->dst->id, amounts[i+1], delays[i+1]);
	json_array_end(response);
	json_object_end(response);
	command_success(cmd, response);
}

const struct json_command getroute_command = {
	"getroute",
	json_getroute,
	"Return route for {msatoshis} to {id}",
	"Returns a {route} array of {id} {msatoshis} {delay}: msatoshis and delay (in blocks) is cumulative."
};

static void json_sendpay(struct command *cmd,
			 const char *buffer, const jsmntok_t *params)
{
	struct pubkey *ids;
	u64 *amounts;
	jsmntok_t *routetok, *rhashtok;
	const jsmntok_t *t, *end;
	unsigned int delay;
	size_t n_hops;
	struct sha256 rhash;
	struct peer *peer;
	struct pay_command *pc;
	const u8 *onion;
	enum fail_error error_code;
	const char *err;

	if (!json_get_params(buffer, params,
			     "route", &routetok,
			     "rhash", &rhashtok,
			     NULL)) {
		command_fail(cmd, "Need route and rhash");
		return;
	}

	if (!hex_decode(buffer + rhashtok->start,
			rhashtok->end - rhashtok->start,
			&rhash, sizeof(rhash))) {
		command_fail(cmd, "'%.*s' is not a valid sha256 hash",
			     (int)(rhashtok->end - rhashtok->start),
			     buffer + rhashtok->start);
		return;
	}

	if (routetok->type != JSMN_ARRAY) {
		command_fail(cmd, "'%.*s' is not an array",
			     (int)(routetok->end - routetok->start),
			     buffer + routetok->start);
		return;
	}

	end = json_next(routetok);
	n_hops = 0;
	amounts = tal_arr(cmd, u64, n_hops);
	ids = tal_arr(cmd, struct pubkey, n_hops);
	for (t = routetok + 1; t < end; t = json_next(t)) {
		const jsmntok_t *amttok, *idtok, *delaytok;

		if (t->type != JSMN_OBJECT) {
			command_fail(cmd, "route %zu '%.*s' is not an object",
				     n_hops,
				     (int)(t->end - t->start),
				     buffer + t->start);
			return;
		}
		amttok = json_get_member(buffer, t, "msatoshis");
		idtok = json_get_member(buffer, t, "id");
		delaytok = json_get_member(buffer, t, "delay");
		if (!amttok || !idtok || !delaytok) {
			command_fail(cmd, "route %zu needs msatoshis/id/delay",
				     n_hops);
			return;
		}

		tal_resize(&amounts, n_hops+1);
		if (!json_tok_u64(buffer, amttok, &amounts[n_hops])) {
			command_fail(cmd, "route %zu invalid msatoshis", n_hops);
			return;
		}
		tal_resize(&ids, n_hops+1);
		if (!pubkey_from_hexstr(cmd->dstate->secpctx,
					buffer + idtok->start,
					idtok->end - idtok->start,
					&ids[n_hops])) {
			command_fail(cmd, "route %zu invalid id", n_hops);
			return;
		}
		/* Only need first delay. */
		if (n_hops == 0 && !json_tok_number(buffer, delaytok, &delay)) {
			command_fail(cmd, "route %zu invalid delay", n_hops);
			return;
		}
		n_hops++;
	}

	if (n_hops == 0) {
		command_fail(cmd, "Empty route");
		return;
	}

	pc = find_pay_command(cmd->dstate, &rhash);
	if (pc) {
		log_debug(cmd->dstate->base_log, "json_sendpay: found previous");
		if (pc->htlc) {
			log_add(cmd->dstate->base_log, "... still in progress");
			command_fail(cmd, "still in progress");
			return;
		}
		if (pc->rval) {
			log_add(cmd->dstate->base_log, "... succeeded");
			/* Must match successful payment parameters. */
			if (pc->msatoshis != amounts[n_hops-1]) {
				command_fail(cmd,
					     "already succeeded with amount %"
					     PRIu64, pc->msatoshis);
				return;
			}
			if (!structeq(&pc->id, &ids[n_hops-1])) {
				char *previd;
				previd = pubkey_to_hexstr(cmd,
							  cmd->dstate->secpctx,
							  &pc->id);
				command_fail(cmd,
					     "already succeeded to %s",
					     previd);
				return;
			}
			json_pay_success(cmd, pc->rval);
			return;
		}
		log_add(cmd->dstate->base_log, "... retrying");
	}

	peer = find_peer(cmd->dstate, &ids[0]);
	if (!peer) {
		command_fail(cmd, "no connection to first peer found");
		return;
	}

	/* Onion will carry us from first peer onwards. */
	onion = onion_create(cmd, cmd->dstate->secpctx, ids+1, amounts+1,
			     n_hops-1);

	if (!pc)
		pc = tal(cmd->dstate, struct pay_command);
	pc->cmd = cmd;
	pc->rhash = rhash;
	pc->rval = NULL;
	pc->id = ids[n_hops-1];
	pc->msatoshis = amounts[n_hops-1];

	/* Expiry for HTLCs is absolute.  And add one to give some margin. */
	err = command_htlc_add(peer, amounts[0],
			       delay + get_block_height(cmd->dstate) + 1,
			       &rhash, NULL,
			       onion, &error_code, &pc->htlc);
	if (err) {
		command_fail(cmd, "could not add htlc: %u: %s", error_code, err);
		return;
	}

	/* Wait until we get response. */
	list_add_tail(&cmd->dstate->pay_commands, &pc->list);
	tal_add_destructor(cmd, remove_cmd_from_pc);
}

const struct json_command sendpay_command = {
	"sendpay",
	json_sendpay,
	"Send along {route} in return for preimage of {rhash}",
	"Returns the {preimage} on success"
};