#include "pay.h"
#include <bitcoin/preimage.h>
#include <ccan/str/hex/hex.h>
#include <ccan/structeq/structeq.h>
#include <ccan/tal/str/str.h>
#include <channeld/gen_channel_wire.h>
#include <common/bolt11.h>
#include <gossipd/gen_gossip_wire.h>
#include <gossipd/routing.h>
#include <inttypes.h>
#include <lightningd/chaintopology.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/lightningd.h>
#include <lightningd/log.h>
#include <lightningd/peer_control.h>
#include <lightningd/peer_htlcs.h>
#include <lightningd/subd.h>
#include <sodium/randombytes.h>

/* Outstanding "pay" commands. */
struct pay_command {
	struct list_node list;
	struct sha256 rhash;
	u64 msatoshi;
	const struct pubkey *ids;
	/* Set if this is in progress. */
	struct htlc_out *out;
	/* Preimage if this succeeded. */
	const struct preimage *rval;
	struct command *cmd;

	/* Remember all shared secrets, so we can unwrap an eventual failure */
	struct secret *path_secrets;
};

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

	/* Can be NULL if JSON RPC goes away. */
	if (!cmd)
		return;

	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 json_pay_failed(struct pay_command *pc,
			    const struct pubkey *sender,
			    enum onion_type failure_code,
			    const char *details)
{
	/* Can be NULL if JSON RPC goes away. */
	if (!pc->cmd)
		return;

	/* FIXME: Report sender! */
	command_fail(pc->cmd, "failed: %s (%s)",
		     onion_type_name(failure_code), details);

	pc->out = NULL;
}

void payment_succeeded(struct lightningd *ld, struct htlc_out *hout,
		       const struct preimage *rval)
{
	assert(!hout->pay_command->rval);
	wallet_payment_set_status(ld->wallet, &hout->payment_hash, PAYMENT_COMPLETE);
	hout->pay_command->rval = tal_dup(hout->pay_command,
					  struct preimage, rval);
	json_pay_success(hout->pay_command->cmd, rval);
	hout->pay_command->out = NULL;
}

void payment_failed(struct lightningd *ld, const struct htlc_out *hout,
		    const char *localfail)
{
	struct pay_command *pc = hout->pay_command;
	struct onionreply *reply;
	enum onion_type failcode;

	wallet_payment_set_status(ld->wallet, &hout->payment_hash, PAYMENT_FAILED);

	/* This gives more details than a generic failure message */
	if (localfail) {
		json_pay_failed(pc, NULL, hout->failcode, localfail);
		return;
	}

	/* Must be remote fail. */
	assert(!hout->failcode);
	reply = unwrap_onionreply(pc, pc->path_secrets,
				  tal_count(pc->path_secrets),
				  hout->failuremsg);
	if (!reply) {
		log_info(hout->key.peer->log,
			 "htlc %"PRIu64" failed with bad reply (%s)",
			 hout->key.id,
			 tal_hex(ltmp, hout->failuremsg));
		failcode = WIRE_PERMANENT_NODE_FAILURE;
	} else {
		failcode = fromwire_peektype(reply->msg);
		log_info(hout->key.peer->log,
			 "htlc %"PRIu64" failed from %ith node with code 0x%04x (%s)",
			 hout->key.id,
			 reply->origin_index,
			 failcode, onion_type_name(failcode));
	}

	/* FIXME: save ids we can turn reply->origin_index into sender. */

	/* FIXME: check for routing failure / perm fail. */
	/* check_for_routing_failure(i, sender, failure_code); */

	json_pay_failed(pc, NULL, failcode, "reply from remote");
}

/* 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)
{
	/* This can be false, in the case where another pay command
	 * re-uses the pc->cmd before we get around to cleaning up. */
	if (pc->cmd == cmd)
		pc->cmd = NULL;
}

static struct pay_command *find_pay_command(struct lightningd *ld,
					    const struct sha256 *rhash)
{
	struct pay_command *pc;

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

static void pay_command_destroyed(struct pay_command *pc)
{
	list_del(&pc->list);
}

/* Returns true if it's still pending. */
static bool send_payment(struct command *cmd,
			 const struct sha256 *rhash,
			 const struct route_hop *route)
{
	struct pay_command *pc;
	struct peer *peer;
	const u8 *onion;
	u8 sessionkey[32];
	unsigned int base_expiry;
	struct onionpacket *packet;
	struct secret *path_secrets;
	enum onion_type failcode;
	/* Freed automatically on cmd completion: only manually at end. */
	const tal_t *tmpctx = tal_tmpctx(cmd);
	size_t i, n_hops = tal_count(route);
	struct hop_data *hop_data = tal_arr(tmpctx, struct hop_data, n_hops);
	struct pubkey *ids = tal_arr(tmpctx, struct pubkey, n_hops);
	struct wallet_payment *payment = NULL;

	/* Expiry for HTLCs is absolute.  And add one to give some margin. */
	base_expiry = get_block_height(cmd->ld->topology) + 1;

	/* Extract IDs for each hop: create_onionpacket wants array. */
	for (i = 0; i < n_hops; i++)
		ids[i] = route[i].nodeid;

	/* Copy hop_data[n] from route[n+1] (ie. where it goes next) */
	for (i = 0; i < n_hops - 1; i++) {
		hop_data[i].realm = 0;
		hop_data[i].channel_id = route[i+1].channel_id;
		hop_data[i].amt_forward = route[i+1].amount;
		hop_data[i].outgoing_cltv = base_expiry + route[i+1].delay;
	}

	/* And finally set the final hop to the special values in
	 * BOLT04 */
	hop_data[i].realm = 0;
	hop_data[i].outgoing_cltv = base_expiry + route[i].delay;
	memset(&hop_data[i].channel_id, 0, sizeof(struct short_channel_id));
	hop_data[i].amt_forward = route[i].amount;

	pc = find_pay_command(cmd->ld, rhash);
	if (pc) {
		log_debug(cmd->ld->log, "json_sendpay: found previous");
		if (pc->out) {
			log_add(cmd->ld->log, "... still in progress");
			command_fail(cmd, "still in progress");
			return false;
		}
		if (pc->rval) {
			size_t old_nhops = tal_count(pc->ids);
			log_add(cmd->ld->log, "... succeeded");
			/* Must match successful payment parameters. */
			if (pc->msatoshi != hop_data[n_hops-1].amt_forward) {
				command_fail(cmd,
					     "already succeeded with amount %"
					     PRIu64, pc->msatoshi);
				return false;
			}
			if (!structeq(&pc->ids[old_nhops-1], &ids[n_hops-1])) {
				char *previd;
				previd = pubkey_to_hexstr(cmd,
							  &pc->ids[old_nhops-1]);
				command_fail(cmd,
					     "already succeeded to %s",
					     previd);
				return false;
			}
			json_pay_success(cmd, pc->rval);
			return false;
		}
		/* FIXME: We can free failed ones... */
		log_add(cmd->ld->log, "... retrying");
	}

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

	randombytes_buf(&sessionkey, sizeof(sessionkey));

	/* Onion will carry us from first peer onwards. */
	packet = create_onionpacket(cmd, ids, hop_data, sessionkey, rhash->u.u8,
				    sizeof(struct sha256), &path_secrets);
	onion = serialize_onionpacket(cmd, packet);

	if (pc) {
		pc->ids = tal_free(pc->ids);
		pc->path_secrets = tal_free(pc->path_secrets);
	} else {
		pc = tal(cmd->ld, struct pay_command);
		list_add_tail(&cmd->ld->pay_commands, &pc->list);
		tal_add_destructor(pc, pay_command_destroyed);

		payment = tal(tmpctx, struct wallet_payment);
		payment->id = 0;
		payment->incoming = false;
		payment->payment_hash = *rhash;
		payment->destination = &ids[n_hops - 1];
		payment->status = PAYMENT_PENDING;
		payment->msatoshi = tal(payment, u64);
		*payment->msatoshi = route[n_hops-1].amount;
		payment->timestamp = time_now().ts.tv_sec;
	}
	pc->cmd = cmd;
	pc->rhash = *rhash;
	pc->rval = NULL;
	pc->ids = tal_steal(pc, ids);
	pc->msatoshi = route[n_hops-1].amount;
	pc->path_secrets = tal_steal(pc, path_secrets);
	pc->out = NULL;

	log_info(cmd->ld->log, "Sending %u over %zu hops to deliver %"PRIu64,
		 route[0].amount, n_hops, pc->msatoshi);

	/* Wait until we get response. */
	tal_add_destructor2(cmd, remove_cmd_from_pc, pc);

	/* They're both children of ld, but on shutdown make sure we
	 * destroy the command before the pc, otherwise the
	 * remove_cmd_from_pc destructor causes a use-after-free */
	tal_steal(pc, cmd);

	failcode = send_htlc_out(peer, route[0].amount,
				 base_expiry + route[0].delay,
				 rhash, onion, NULL, payment, pc,
				 &pc->out);
	if (failcode) {
		command_fail(cmd, "first peer not ready: %s",
			     onion_type_name(failcode));
		return false;
	}
	tal_free(tmpctx);
	return true;
}

static void json_sendpay(struct command *cmd,
			 const char *buffer, const jsmntok_t *params)
{
	jsmntok_t *routetok, *rhashtok;
	const jsmntok_t *t, *end;
	size_t n_hops;
	struct sha256 rhash;
	struct route_hop *route;

	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;
	route = tal_arr(cmd, struct route_hop, n_hops);

	for (t = routetok + 1; t < end; t = json_next(t)) {
		const jsmntok_t *amttok, *idtok, *delaytok, *chantok;

		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, "msatoshi");
		idtok = json_get_member(buffer, t, "id");
		delaytok = json_get_member(buffer, t, "delay");
		chantok = json_get_member(buffer, t, "channel");
		if (!amttok || !idtok || !delaytok || !chantok) {
			command_fail(cmd, "route %zu needs msatoshi/id/channel/delay",
				     n_hops);
			return;
		}

		tal_resize(&route, n_hops + 1);

		/* What that hop will forward */
		if (!json_tok_number(buffer, amttok, &route[n_hops].amount)) {
			command_fail(cmd, "route %zu invalid msatoshi",
				     n_hops);
			return;
		}

		if (!short_channel_id_from_str(buffer + chantok->start,
					       chantok->end - chantok->start,
					       &route[n_hops].channel_id)) {
			command_fail(cmd, "route %zu invalid channel_id", n_hops);
			return;
		}
		if (!json_tok_pubkey(buffer, idtok, &route[n_hops].nodeid)) {
			command_fail(cmd, "route %zu invalid id", n_hops);
			return;
		}
		if (!json_tok_number(buffer, delaytok, &route[n_hops].delay)) {
			command_fail(cmd, "route %zu invalid delay", n_hops);
			return;
		}
		n_hops++;
	}

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

	if (send_payment(cmd, &rhash, route))
		command_still_pending(cmd);
}

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

struct pay {
	struct sha256 payment_hash;
	struct command *cmd;
};

static void json_pay_getroute_reply(struct subd *gossip,
				    const u8 *reply, const int *fds,
				    struct pay *pay)
{
	struct route_hop *route;

	fromwire_gossip_getroute_reply(reply, reply, NULL, &route);

	if (tal_count(route) == 0) {
		command_fail(pay->cmd, "Could not find a route");
		return;
	}

	send_payment(pay->cmd, &pay->payment_hash, route);
}

static void json_pay(struct command *cmd,
		     const char *buffer, const jsmntok_t *params)
{
	jsmntok_t *bolt11tok, *msatoshitok, *desctok, *riskfactortok;
	double riskfactor = 1.0;
	u64 msatoshi;
	struct pay *pay = tal(cmd, struct pay);
	struct bolt11 *b11;
	char *fail, *b11str, *desc;
	u8 *req;

	if (!json_get_params(buffer, params,
			     "bolt11", &bolt11tok,
			     "?msatoshi", &msatoshitok,
			     "?description", &desctok,
			     "?riskfactor", &riskfactortok,
			     NULL)) {
		command_fail(cmd, "Need bolt11 string");
		return;
	}

	b11str = tal_strndup(cmd, buffer + bolt11tok->start,
			     bolt11tok->end - bolt11tok->start);
	if (desctok)
		desc = tal_strndup(cmd, buffer + desctok->start,
				   desctok->end - desctok->start);
	else
		desc = NULL;

	b11 = bolt11_decode(pay, b11str, desc, &fail);
	if (!b11) {
		command_fail(cmd, "Invalid bolt11: %s", fail);
		return;
	}

	pay->cmd = cmd;
	pay->payment_hash = b11->payment_hash;

	if (b11->msatoshi) {
		msatoshi = *b11->msatoshi;
		if (msatoshitok && !json_tok_is_null(buffer, msatoshitok)) {
			command_fail(cmd, "msatoshi parameter unnecessary");
			return;
		}
	} else {
		if (!msatoshitok) {
			command_fail(cmd, "msatoshi parameter required");
			return;
		}
		if (!json_tok_u64(buffer, msatoshitok, &msatoshi)) {
			command_fail(cmd,
				     "msatoshi '%.*s' is not a valid number",
				     (int)(msatoshitok->end-msatoshitok->start),
				     buffer + msatoshitok->start);
			return;
		}
	}

	if (riskfactortok
	    && !json_tok_double(buffer, riskfactortok, &riskfactor)) {
		command_fail(cmd, "'%.*s' is not a valid double",
			     (int)(riskfactortok->end - riskfactortok->start),
			     buffer + riskfactortok->start);
		return;
	}

	/* FIXME: use b11->routes */
	req = towire_gossip_getroute_request(cmd, &cmd->ld->id,
					     &b11->receiver_id,
					     msatoshi, riskfactor*1000,
					     b11->min_final_cltv_expiry);
	subd_req(pay, cmd->ld->gossip, req, -1, 0, json_pay_getroute_reply, pay);
	command_still_pending(cmd);
}

static const struct json_command pay_command = {
	"pay",
	json_pay,
	"Send payment specified by {bolt11} with optional {msatoshi} (iff {bolt11} does not have amount), {description} (required if {bolt11} uses description hash) and {riskfactor} (default 1.0)",
	"Returns the {preimage} on success"
};
AUTODATA(json_command, &pay_command);

static void json_listpayments(struct command *cmd, const char *buffer,
			       const jsmntok_t *params)
{
	const struct wallet_payment **payments;
	struct json_result *response = new_json_result(cmd);

	payments = wallet_payment_list(cmd, cmd->ld->wallet);

	json_array_start(response, NULL);
	for (int i=0; i<tal_count(payments); i++) {
		const struct wallet_payment *t = payments[i];
		json_object_start(response, NULL);
		json_add_u64(response, "id", t->id);
		json_add_bool(response, "incoming", t->incoming);
		json_add_hex(response, "payment_hash", &t->payment_hash, sizeof(t->payment_hash));
		if (!t->incoming)
			json_add_pubkey(response, "destination", t->destination);
		if (t->msatoshi)
			json_add_u64(response, "msatoshi", *t->msatoshi);
		json_add_u64(response, "timestamp", t->timestamp);

		switch (t->status) {
		case PAYMENT_PENDING:
			json_add_string(response, "status", "pending");
			break;
		case PAYMENT_COMPLETE:
			json_add_string(response, "status", "complete");
			break;
		case PAYMENT_FAILED:
			json_add_string(response, "status", "failed");
			break;
		}

		json_object_end(response);
	}
	json_array_end(response);
	command_success(cmd, response);
}

static const struct json_command listpayments_command = {
	"listpayments",
	json_listpayments,
	"Get a list of incoming and outgoing payments",
	"Returns a list of payments with {direction}, {payment_hash}, {destination} if outgoing and {msatoshi}"
};
AUTODATA(json_command, &listpayments_command);