#include "config.h"
#include <stdio.h>
#include "../bolt12_proof.c"
#include "../bolt12_merkle.c"
#include "../bech32_util.c"
#include "../bech32.c"
#include "../json_parse.c"
#include "../json_parse_simple.c"
#include <ccan/ptrint/ptrint.h>
#include <common/features.h>
#include <common/setup.h>
#include <secp256k1_schnorrsig.h>

/* AUTOGENERATED MOCKS START */
/* Generated stub for features_unsupported */
int features_unsupported(const struct feature_set *our_features UNNEEDED,
			 const u8 *their_features UNNEEDED,
			 enum feature_place p UNNEEDED)
{ fprintf(stderr, "features_unsupported called!\n"); abort(); }
/* Generated stub for mvt_tag_parse */
bool mvt_tag_parse(const char *buf UNNEEDED, size_t len UNNEEDED, enum mvt_tag *tag UNNEEDED)
{ fprintf(stderr, "mvt_tag_parse called!\n"); abort(); }
/* Generated stub for node_id_from_hexstr */
bool node_id_from_hexstr(const char *str UNNEEDED, size_t slen UNNEEDED, struct node_id *id UNNEEDED)
{ fprintf(stderr, "node_id_from_hexstr called!\n"); abort(); }
/* Generated stub for pubkey_from_node_id */
bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED)
{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); }
/* Generated stub for siphash_seed */
const struct siphash_seed *siphash_seed(void)
{ fprintf(stderr, "siphash_seed called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */

/* AAAA... BBBB... etc */
static struct pubkey *pubkey_for_letter(const tal_t *ctx, char letter)
{
	struct secret secret;
	struct pubkey *pk;

	pk = tal(ctx, struct pubkey);
	memset(&secret, letter, sizeof(secret));
	assert(pubkey_from_secret(&secret, pk));
	return pk;
}

static secp256k1_keypair keypair_for_letter(char letter)
{
	struct secret secret;
	secp256k1_keypair kp;

	memset(&secret, letter, sizeof(secret));

	if (secp256k1_keypair_create(secp256k1_ctx, &kp,
				     secret.data) != 1)
		abort();
	return kp;
}

static struct bip340sig *invoice_signature(const tal_t *ctx, struct tlv_invoice *inv, char letter)
{
	struct sha256 merkle, sha;
	struct bip340sig *sig;
	secp256k1_keypair kp = keypair_for_letter(letter);

	/* Update fields[] array from our settings */
	tlv_update_fields(inv, tlv_invoice, &inv->fields);
	merkle_tlv(inv->fields, &merkle);
	inv->signature = tal(inv, struct bip340sig);
	sighash_from_merkle("invoice", "signature", &merkle, &sha);

	sig = tal(ctx, struct bip340sig);
	assert(secp256k1_schnorrsig_sign32(secp256k1_ctx, sig->u8,
					   sha.u.u8,
					   &kp,
					   NULL) == 1);
	return sig;
}

/* Returns true to include the field */
static bool exclude_this(const struct tlv_field *f, ptrint_t *p)
{
	/* We always exclude 0! */
	if (f->numtype == 0)
		return false;
	if (f->numtype == ptr2int(p))
		return false;
	return true;
}

static bool sign(const char *messagename,
		 const char *fieldname,
		 const struct sha256 *msg,
		 struct bip340sig *sig,
		 secp256k1_keypair *kp)
{
	struct sha256 shash;

	sighash_from_merkle(messagename, fieldname, msg, &shash);
	return secp256k1_schnorrsig_sign32(secp256k1_ctx, sig->u8,
					   shash.u.u8,
					   kp,
					   NULL) == 1;
}

int main(int argc, char *argv[])
{
	struct tlv_invoice *inv;
	struct preimage preimage;
	struct tlv_payer_proof *proof;
	const char *invstr, *fail;
	secp256k1_keypair kp;

	common_setup(argv[0]);

	memset(&preimage, 0x1, sizeof(preimage));

	/* Minimal invoice */
	inv = tlv_invoice_new(tmpctx);
	inv->invreq_metadata = tal_arrz(inv, u8, 16);
	inv->offer_issuer_id = pubkey_for_letter(inv, 'A');

	inv->invreq_amount = tal(inv, u64);
	*inv->invreq_amount = 1;
	inv->invreq_payer_id = pubkey_for_letter(inv, 'B');

	inv->invoice_paths = tal_arr(inv, struct blinded_path *, 1);
	inv->invoice_paths[0] = tal(inv->invoice_paths, struct blinded_path);
	sciddir_or_pubkey_from_pubkey(&inv->invoice_paths[0]->first_node_id,
				      pubkey_for_letter(tmpctx, 'C'));
	inv->invoice_paths[0]->first_path_key = *pubkey_for_letter(tmpctx, 'D');
	inv->invoice_paths[0]->path = tal_arr(inv->invoice_paths[0], struct blinded_path_hop *, 1);
	inv->invoice_paths[0]->path[0] = tal(inv->invoice_paths[0]->path,
					     struct blinded_path_hop);
	inv->invoice_paths[0]->path[0]->blinded_node_id = *pubkey_for_letter(tmpctx, 'E');
	inv->invoice_paths[0]->path[0]->encrypted_recipient_data = tal_arrz(inv->invoice_paths[0]->path[0], u8, 16);
	inv->invoice_blindedpay = tal_arr(inv, struct blinded_payinfo *, 1);
	inv->invoice_blindedpay[0] = tal(inv->invoice_blindedpay, struct blinded_payinfo);
	inv->invoice_blindedpay[0]->fee_base_msat = 1;
	inv->invoice_blindedpay[0]->fee_proportional_millionths = 2;
	inv->invoice_blindedpay[0]->cltv_expiry_delta = 3;
	inv->invoice_blindedpay[0]->htlc_minimum_msat = AMOUNT_MSAT(4);
	inv->invoice_blindedpay[0]->htlc_maximum_msat = AMOUNT_MSAT(5);
	inv->invoice_blindedpay[0]->features = NULL;

	inv->invoice_created_at = tal(inv, u64);
	*inv->invoice_created_at = 1733458312;
	inv->invoice_payment_hash = tal(inv, struct sha256);
	sha256(inv->invoice_payment_hash, &preimage, sizeof(preimage));
	inv->invoice_amount = tal(inv, u64);
	*inv->invoice_amount = 1;
	inv->invoice_node_id = pubkey_for_letter(inv, 'F');

	inv->signature = invoice_signature(inv, inv, 'F');

	/* We did OK, right?  Also, canonicalizes. */
	invstr = invoice_encode(tmpctx, inv);
	inv = invoice_decode(tmpctx, invstr, strlen(invstr), NULL, NULL, &fail);
	assert(inv);

	/* OK, make a proof (include everything) */
	proof = make_unsigned_proof(tmpctx, inv, &preimage, "test",
				    exclude_this, int2ptr(0));
	kp = keypair_for_letter('B');
	proof->proof_signature = payer_proof_signature(proof, proof, sign, &kp);
	assert(check_payer_proof(tmpctx, proof) == NULL);

	/* For each field, try excluding it */
	for (size_t i = 0; i < tal_count(inv->fields); i++) {
		const char *note = tal_fmt(tmpctx, "test-exclude-%zu", i);
		proof = make_unsigned_proof(tmpctx, inv, &preimage, note, exclude_this, int2ptr(i));
		proof->proof_signature = payer_proof_signature(proof, proof, sign, &kp);
		assert(check_payer_proof(tmpctx, proof) == NULL);
	}

	/* Make sure that full proof notices if any field is altered! */
	for (size_t i = 0;; i++) {
		u8 *tlvstream;
		const u8 *p;
		size_t len;
		struct tlv_payer_proof *tlvpp;
		struct sha256 merkle, shash;

		proof = make_unsigned_proof(tmpctx, inv, &preimage, "test",
					    exclude_this, int2ptr(0));
		proof->proof_signature = payer_proof_signature(proof, proof, sign,
							       &kp);
		tlv_update_fields(proof, tlv_payer_proof, &proof->fields);

		if (i == tal_count(proof->fields))
			break;

		switch (proof->fields[i].numtype) {
		/* Simple ones: we can increment the first byte to
		 * change the value (and have it still decode) */
		case TLV_PAYER_PROOF_OFFER_CHAINS:
		case TLV_PAYER_PROOF_OFFER_METADATA:
		case TLV_PAYER_PROOF_OFFER_CURRENCY:
		case TLV_PAYER_PROOF_OFFER_AMOUNT:
		case TLV_PAYER_PROOF_OFFER_DESCRIPTION:
		case TLV_PAYER_PROOF_OFFER_ABSOLUTE_EXPIRY:
		case TLV_PAYER_PROOF_OFFER_ISSUER:
		case TLV_PAYER_PROOF_OFFER_QUANTITY_MAX:
		case TLV_PAYER_PROOF_INVREQ_CHAIN:
		case TLV_PAYER_PROOF_INVREQ_AMOUNT:
		case TLV_PAYER_PROOF_INVREQ_QUANTITY:
		case TLV_PAYER_PROOF_INVREQ_PAYER_NOTE:
		case TLV_PAYER_PROOF_INVREQ_BIP_353_NAME:
		case TLV_PAYER_PROOF_INVOICE_CREATED_AT:
		case TLV_PAYER_PROOF_INVOICE_RELATIVE_EXPIRY:
		case TLV_PAYER_PROOF_INVOICE_AMOUNT:
		case TLV_PAYER_PROOF_PROOF_NOTE:
		case TLV_PAYER_PROOF_INVOICE_PAYMENT_HASH:
		case TLV_PAYER_PROOF_PROOF_PREIMAGE:
			proof->fields[i].value[0]++;
			break;

		/* Put in random signature */
		case TLV_PAYER_PROOF_SIGNATURE:
		case TLV_PAYER_PROOF_PROOF_SIGNATURE: {
			struct bip340sig sig;
			struct sha256 msg;
			memset(&msg, 0, sizeof(msg));
			sign("", "", &msg, &sig, &kp);
			assert(proof->fields[i].length == sizeof(sig));
			memcpy(proof->fields[i].value, &sig, sizeof(sig));
			break;
		}
		/* Flip bit 1 here */
		case TLV_PAYER_PROOF_INVOICE_FEATURES:
		case TLV_PAYER_PROOF_OFFER_FEATURES:
		case TLV_PAYER_PROOF_INVREQ_FEATURES:
			proof->fields[i].value[0] ^= 2;
			break;

		/* Truncate these */
		case TLV_PAYER_PROOF_INVREQ_PATHS:
		case TLV_PAYER_PROOF_INVOICE_PATHS:
		case TLV_PAYER_PROOF_PROOF_OMITTED_TLVS:
		case TLV_PAYER_PROOF_PROOF_MISSING_HASHES:
		case TLV_PAYER_PROOF_PROOF_LEAF_HASHES:
		case TLV_PAYER_PROOF_INVOICE_FALLBACKS:
		case TLV_PAYER_PROOF_INVOICE_BLINDEDPAY:
		case TLV_PAYER_PROOF_OFFER_PATHS:
			proof->fields[i].length = 0;
			break;

		/* Replace with random pubkey */
		case TLV_PAYER_PROOF_INVOICE_NODE_ID:
		case TLV_PAYER_PROOF_INVREQ_PAYER_ID:
		case TLV_PAYER_PROOF_OFFER_ISSUER_ID: {
			struct secret secret;
			struct pubkey pk;

			memset(&secret, 7, sizeof(secret));
			pubkey_from_secret(&secret, &pk);
			assert(proof->fields[i].length == PUBKEY_CMPR_LEN);
			pubkey_to_der(proof->fields[i].value, &pk);
			break;
		}
		}

		tlvstream = tal_arr(tmpctx, u8, 0);
		towire_tlvstream_raw(&tlvstream, proof->fields);

		/* Should demarshal OK */
		len = tal_bytelen(tlvstream);
		p = tlvstream;
		tlvpp = fromwire_tlv_payer_proof(tmpctx, &p, &len);
		assert(tlvpp);
		assert(p != NULL);
		assert(len == 0);

		/* Proof sig should be invalid if we changed anything!*/
		bolt12_payer_proof_merkle(tlvpp, &merkle);
		sighash_from_merkle("payer_proof", "proof_signature",
				    &merkle, &shash);

		/* Except the invoice signature (that's checked separately). */
		if (!check_schnorr_sig(&shash, &tlvpp->invreq_payer_id->pubkey,
				       tlvpp->proof_signature)) {
			assert(proof->fields[i].numtype != TLV_PAYER_PROOF_SIGNATURE);
		} else {
			assert(proof->fields[i].numtype == TLV_PAYER_PROOF_SIGNATURE);
		}
	}

	common_shutdown();
	return 0;
}
