#include "config.h"
#include "plugins/channel_hint.h"
#define TESTING
#include "../../common/dijkstra.c"
#include "../libplugin-pay.c"
#include <bitcoin/chainparams.h>
#include <ccan/array_size/array_size.h>
#include <ccan/crc32c/crc32c.h>
#include <common/gossip_store.h>
#include <common/gossip_store_wiregen.h>
#include <common/setup.h>
#include <common/utils.h>
#include <stdio.h>
#include <unistd.h>

/* AUTOGENERATED MOCKS START */
/* Generated stub for aux_command */
struct command *aux_command(const struct command *cmd)

{ fprintf(stderr, "aux_command called!\n"); abort(); }
/* Generated stub for command_check_only */
bool command_check_only(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_check_only called!\n"); abort(); }
/* Generated stub for command_dev_apis */
bool command_dev_apis(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_dev_apis called!\n"); abort(); }
/* Generated stub for command_fail */
struct command_result *command_fail(struct command *cmd UNNEEDED, enum jsonrpc_errcode code UNNEEDED,
				    const char *fmt UNNEEDED, ...)

{ fprintf(stderr, "command_fail called!\n"); abort(); }
/* Generated stub for command_filter_ptr */
struct json_filter **command_filter_ptr(struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_filter_ptr called!\n"); abort(); }
/* Generated stub for command_finished */
struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response)

{ fprintf(stderr, "command_finished called!\n"); abort(); }
/* Generated stub for command_log */
void command_log(struct command *cmd UNNEEDED, enum log_level level UNNEEDED,
		 const char *fmt UNNEEDED, ...)

{ fprintf(stderr, "command_log called!\n"); abort(); }
/* Generated stub for command_still_pending */
struct command_result *command_still_pending(struct command *cmd)

{ fprintf(stderr, "command_still_pending called!\n"); abort(); }
/* Generated stub for json_to_createonion_response */
struct createonion_response *json_to_createonion_response(const tal_t *ctx UNNEEDED,
							  const char *buffer UNNEEDED,
							  const jsmntok_t *toks UNNEEDED)
{ fprintf(stderr, "json_to_createonion_response called!\n"); abort(); }
/* Generated stub for json_to_listpeers_channels */
struct listpeers_channel **json_to_listpeers_channels(const tal_t *ctx UNNEEDED,
						      const char *buffer UNNEEDED,
						      const jsmntok_t *tok UNNEEDED)
{ fprintf(stderr, "json_to_listpeers_channels called!\n"); abort(); }
/* Generated stub for jsonrpc_request_start_ */
struct out_req *jsonrpc_request_start_(struct command *cmd UNNEEDED,
				       const char *method UNNEEDED,
				       const char *id_prefix UNNEEDED,
				       const char *filter UNNEEDED,
				       struct command_result *(*cb)(struct command *command UNNEEDED,
								    const char *methodname UNNEEDED,
								    const char *buf UNNEEDED,
								    const jsmntok_t *result UNNEEDED,
								    void *arg) UNNEEDED,
				       struct command_result *(*errcb)(struct command *command UNNEEDED,
								       const char *methodname UNNEEDED,
								       const char *buf UNNEEDED,
								       const jsmntok_t *result UNNEEDED,
								       void *arg) UNNEEDED,
				       void *arg)

{ fprintf(stderr, "jsonrpc_request_start_ called!\n"); abort(); }
/* Generated stub for jsonrpc_stream_fail */
struct json_stream *jsonrpc_stream_fail(struct command *cmd UNNEEDED,
					int code UNNEEDED,
					const char *err)

{ fprintf(stderr, "jsonrpc_stream_fail called!\n"); abort(); }
/* Generated stub for jsonrpc_stream_success */
struct json_stream *jsonrpc_stream_success(struct command *cmd)

{ fprintf(stderr, "jsonrpc_stream_success called!\n"); abort(); }
/* Generated stub for notification_deprecated_out_ok */
bool notification_deprecated_out_ok(struct plugin *plugin UNNEEDED,
				    const char *method UNNEEDED,
				    const char *fieldname UNNEEDED,
				    const char *depr_start UNNEEDED,
				    const char *depr_end UNNEEDED)
{ fprintf(stderr, "notification_deprecated_out_ok called!\n"); abort(); }
/* Generated stub for plugin_err */
void   plugin_err(struct plugin *p UNNEEDED, const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "plugin_err called!\n"); abort(); }
/* Generated stub for plugin_gossmap_logcb */
void plugin_gossmap_logcb(struct plugin *plugin UNNEEDED,
			  enum log_level level UNNEEDED,
			  const char *fmt UNNEEDED,
			  ...)
{ fprintf(stderr, "plugin_gossmap_logcb called!\n"); abort(); }
/* Generated stub for plugin_log */
void plugin_log(struct plugin *p UNNEEDED, enum log_level l UNNEEDED, const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "plugin_log called!\n"); abort(); }
/* Generated stub for plugin_notification_end_obs */
void plugin_notification_end_obs(struct plugin *plugin UNNEEDED,
				 struct json_stream *stream TAKES UNNEEDED)
{ fprintf(stderr, "plugin_notification_end_obs called!\n"); abort(); }
/* Generated stub for plugin_notification_start_obs */
struct json_stream *plugin_notification_start_obs(const tal_t *ctx UNNEEDED,
						  const char *method UNNEEDED)
{ fprintf(stderr, "plugin_notification_start_obs called!\n"); abort(); }
/* Generated stub for plugin_notify_message */
void plugin_notify_message(struct command *cmd UNNEEDED,
			   enum log_level level UNNEEDED,
			   const char *fmt UNNEEDED, ...)

{ fprintf(stderr, "plugin_notify_message called!\n"); abort(); }
/* Generated stub for send_outreq */
struct command_result *send_outreq(const struct out_req *req UNNEEDED)
{ fprintf(stderr, "send_outreq called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */

#ifndef SUPERVERBOSE
#define SUPERVERBOSE(...)
#endif

static void write_to_store(int store_fd, const u8 *msg)
{
	struct gossip_hdr hdr;

	hdr.flags = cpu_to_be16(GOSSIP_STORE_COMPLETED_BIT);
	hdr.len = cpu_to_be16(tal_count(msg));
	hdr.timestamp = 0;
	hdr.crc = cpu_to_be32(crc32c(be32_to_cpu(hdr.timestamp), msg, tal_count(msg)));
	assert(write(store_fd, &hdr, sizeof(hdr)) == sizeof(hdr));
	assert(write(store_fd, msg, tal_count(msg)) == tal_count(msg));
}

static void update_connection(int store_fd,
			      const struct node_id *from,
			      const struct node_id *to,
			      struct short_channel_id scid,
			      struct amount_msat min,
			      struct amount_msat max,
			      u32 base_fee, s32 proportional_fee,
			      u32 delay,
			      bool disable)
{
	secp256k1_ecdsa_signature dummy_sig;
	u8 flags = node_id_idx(from, to);
	u8 *msg;

	if (disable)
		flags |= ROUTING_FLAGS_DISABLED;

	/* So valgrind doesn't complain */
	memset(&dummy_sig, 0, sizeof(dummy_sig));

	msg = towire_channel_update(tmpctx,
				    &dummy_sig,
				    &chainparams->genesis_blockhash,
				    scid, 0,
				    ROUTING_OPT_HTLC_MAX_MSAT,
				    flags,
				    delay,
				    min,
				    base_fee,
				    proportional_fee,
				    max);

	write_to_store(store_fd, msg);
}

static void node_id(char n, struct node_id *id)
{
	struct privkey p;
	struct pubkey k;
	memset(&p, n, sizeof(p));
	pubkey_from_privkey(&p, &k);
	node_id_from_pubkey(id, &k);
}

static struct short_channel_id_dir add_connection(int store_fd, char from, char to,
						  u32 base_fee,
						  s32 proportional_fee,
						  u32 delay,
						  struct amount_sat capacity)
{
	secp256k1_ecdsa_signature dummy_sig;
	struct secret not_a_secret;
	struct pubkey dummy_key;
	u8 *msg;
	struct node_id from_id, to_id;
	const struct node_id *ids[2];
	static struct short_channel_id_dir scidd;

	/* So valgrind doesn't complain */
	memset(&dummy_sig, 0, sizeof(dummy_sig));
	memset(&not_a_secret, 1, sizeof(not_a_secret));
	pubkey_from_secret(&not_a_secret, &dummy_key);

	node_id(from, &from_id);
	node_id(to, &to_id);

	if (node_id_cmp(&from_id, &to_id) > 0) {
		scidd.dir = 1;
		ids[0] = &to_id;
		ids[1] = &from_id;
	} else {
		scidd.dir = 0;
		ids[0] = &from_id;
		ids[1] = &to_id;
	}
	scidd.scid.u64++;
	msg = towire_channel_announcement(tmpctx, &dummy_sig, &dummy_sig,
					  &dummy_sig, &dummy_sig,
					  /* features */ NULL,
					  &chainparams->genesis_blockhash,
					  scidd.scid,
					  ids[0], ids[1],
					  &dummy_key, &dummy_key);
	write_to_store(store_fd, msg);
	msg = towire_gossip_store_channel_amount(tmpctx, capacity);
	write_to_store(store_fd, msg);

	update_connection(store_fd, &from_id, &to_id, scidd.scid,
			  AMOUNT_MSAT(0),
			  amount_msat(capacity.satoshis * 1000),
			  base_fee, proportional_fee,
			  delay, false);
	return scidd;
}

/* A -> B -> C -> D -> E
 *
 * Each node has two channels with the next, one has worse characteristics.
 */
int main(int argc, char *argv[])
{
	struct payment *p;
	struct payment_modifier **mods;
	struct channel_hint_set *hints;

	common_setup(argv[0]);
	chainparams = chainparams_for_network("regtest");
	hints = channel_hint_set_new(tmpctx);

	mods = tal_arrz(tmpctx, struct payment_modifier *, 1);
	p = payment_new(mods, tal(tmpctx, struct command), NULL, hints, mods);

	/* We want to permute order of channels between each node, to
	 * avoid "it works because it chooses the first one!" */
	for (size_t i = 0; i < 2; i++) {
		const char gossip_version = 10;
		struct gossmap *gossmap;
		char *gossipfilename;
		int store_fd;
		struct short_channel_id_dir scids[2], path[4];
		struct route_hop *r;
		struct node_id src, dst;
		const struct dijkstra *dij;
		const char *errmsg;
		u32 risk_factor = 10;

		store_fd = tmpdir_mkstemp(tmpctx, "run-route-calc.XXXXXX",
					  &gossipfilename);
		assert(write(store_fd, &gossip_version, sizeof(gossip_version))
		       == sizeof(gossip_version));

		/* Cheap base vs expensive base */
		scids[0] = add_connection(store_fd, 'A', 'B',
					  1000 * !i,
					  100,
					  1,
					  AMOUNT_SAT(100000000));
		scids[1] = add_connection(store_fd, 'A', 'B',
					  1000 * !!i,
					  100,
					  1,
					  AMOUNT_SAT(100000000));
		path[0] = scids[!i];

		/* Cheap proportion vs expensive proportion */
		scids[0] = add_connection(store_fd, 'B', 'C',
					  1000,
					  100 * !i,
					  1,
					  AMOUNT_SAT(100000000));
		scids[1] = add_connection(store_fd, 'B', 'C',
					  1000,
					  100 * !!i,
					  1,
					  AMOUNT_SAT(100000000));
		path[1] = scids[!i];

		/* Low delay vs high delay */
		scids[0] = add_connection(store_fd, 'C', 'D',
					  1000,
					  100,
					  10 * !i,
					  AMOUNT_SAT(100000000));
		scids[1] = add_connection(store_fd, 'C', 'D',
					  1000,
					  100,
					  10 * !!i,
					  AMOUNT_SAT(100000000));
		path[2] = scids[!i];

		/* High capacity vs low capacity */
		scids[0] = add_connection(store_fd, 'D', 'E',
					  1000,
					  100,
					  1,
					  amount_sat(100 + 100000000 * !!i));
		scids[1] = add_connection(store_fd, 'D', 'E',
					  1000,
					  100,
					  1,
					  amount_sat(100 + 100000000 * !i));
		path[3] = scids[!i];

		gossmap = gossmap_load(tmpctx, gossipfilename, NULL, NULL);
		global_gossmap = gossmap;

		node_id('A', &src);
		node_id('E', &dst);

		/* First check high level code gives correct answer */
		r = route(tmpctx, gossmap,
			  gossmap_find_node(gossmap, &src),
			  gossmap_find_node(gossmap, &dst),
			  /* 80 sats: should bias us against 100 sat channel */
			  AMOUNT_MSAT(80000),
			  /* Final delay */
			  22,
			  risk_factor,
			  /* Max hops */
			  ARRAY_SIZE(path) + 1,
			  p,
			  &errmsg);

		assert(r);
		assert(errmsg == NULL);
		assert(tal_count(r) == ARRAY_SIZE(path));

		for (size_t j = 0; j < ARRAY_SIZE(path); j++) {
			assert(short_channel_id_eq(r[j].scid, path[j].scid));
			assert(r[j].direction == path[j].dir);
		}

		/* Now check Dijkstra directly */
		dij = dijkstra(tmpctx, gossmap, gossmap_find_node(gossmap, &dst),
			       AMOUNT_MSAT(80000),
			       risk_factor,
			       payment_route_can_carry, route_score, p);

		/* It's a line, distances should decrement */
		assert(dijkstra_distance(dij, 0) == 4);
		assert(dijkstra_distance(dij, 1) == 3);
		assert(dijkstra_distance(dij, 2) == 2);
		assert(dijkstra_distance(dij, 3) == 1);
		assert(dijkstra_distance(dij, 4) == 0);

		/* Cost should climb as we go. */
		struct amount_msat amount = AMOUNT_MSAT(80000);
		assert(amount_msat_eq(dij[4].amount, amount));
		assert(amount_msat_add_fee(&amount, 1000, 100));
		assert(amount_msat_eq(dij[3].amount, amount));
		assert(amount_msat_add_fee(&amount, 1000, 100));
		assert(amount_msat_eq(dij[2].amount, amount));
		assert(amount_msat_add_fee(&amount, 1000, 0));
		assert(amount_msat_eq(dij[1].amount, amount));
		assert(amount_msat_add_fee(&amount, 0, 100));
		assert(amount_msat_eq(dij[0].amount, amount));

		/* Similarly, score should climb as we go */
		u64 score = 0;
		struct amount_msat fee;
		assert(dij[4].score == score);
		assert(amount_msat_fee(&fee, dij[3].amount, 1000, 100));
		score += route_score(fee,
				     risk_price(dij[3].amount, risk_factor, 1),
				     dij[3].amount,
				     path[3].dir,
				     gossmap_find_chan(gossmap, &path[3].scid));
		assert(dij[3].score == score);
		assert(amount_msat_fee(&fee, dij[2].amount, 1000, 100));
		score += route_score(fee,
				     risk_price(dij[2].amount, risk_factor, 1),
				     dij[2].amount,
				     path[2].dir,
				     gossmap_find_chan(gossmap, &path[2].scid));
		assert(dij[2].score == score);
		assert(amount_msat_fee(&fee, dij[1].amount, 1000, 0));
		score += route_score(fee,
				     risk_price(dij[1].amount, risk_factor, 0),
				     dij[1].amount,
				     path[1].dir,
				     gossmap_find_chan(gossmap, &path[1].scid));
		assert(dij[1].score == score);
		assert(amount_msat_fee(&fee, dij[0].amount, 0, 100));
		score += route_score(fee,
				     risk_price(dij[0].amount, risk_factor, 1),
				     dij[0].amount,
				     path[0].dir,
				     gossmap_find_chan(gossmap, &path[0].scid));
		assert(dij[0].score == score);

		/*
		 * Test for a 'NaN-cast' bug in route_score().
		 *
		 * This test reproduces a bug that occurs when attempting to
		 * route a payment whose amount exceeds the capacity of the channel
		 * it's routed through. The expected behavior is for route() to
		 * return NULL and set errmsg to "No path found".
		 *
		 * However, due to the imprecision of the htlc_max type (fp16_t), the
		 * channel is not correctly discarded. This causes the route's score
		 * to be calculated as NaN, and when this NaN is subsequently cast to
		 * a u64, it results in a runtime error.
		 *
		 * The expected UBSan error is:
		 * runtime error: nan is outside the range of representable values of type 'unsigned long'
		 */
		add_connection(store_fd, 'X', 'Y',
				/* base fee */ 40333,
				/* prop fee */ 57981,
				/* delay */ 138,
				/* capacity */ AMOUNT_SAT(7875));

		node_id('X', &src);
		node_id('Y', &dst);

		gossmap_refresh(global_gossmap);

		r = route(tmpctx, gossmap,
				gossmap_find_node(gossmap, &src),
				gossmap_find_node(gossmap, &dst),
				/* amount */ AMOUNT_MSAT(7876357),
				/* Final delay */ 2655,
				/* riskfactor */ 57.00,
				/* Max hops */ ROUTING_MAX_HOPS,
				/* payment */ p,
				&errmsg);
	}

	common_shutdown();
	return 0;
}
