#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "policy.h"

#include "test-utils.h"

#define QUORUM(size, ...) size, (LITERAL_STATIC const uint8_t[size]){ __VA_ARGS__}

/* Expected results from parsing. */
struct expect
{
  unsigned num_logs;
  unsigned num_witnesses;
  unsigned quorum_length;
  const uint8_t *quorum_bytecode;
  unsigned quorum_stack_size;
};

static void
test_one (const char *desc, bool verbose, size_t size, const char *input, const struct expect *exp)
{
  struct sigsum_policy policy;
  struct sigsum_error err;
  if (!sigsum_policy_parse (&policy, size, input, &err))
    die ("Parsing policy %s failed: %zu: %s", desc, err.line, err.msg);

  if (verbose)
    {
      fprintf (stderr, "%s witnesses:\n", desc);
      for (unsigned j = 0; j < policy.num_witnesses; j++)
	{
	  fprintf (stderr, "key  %u ", j);
	  dump_hex (stderr, sizeof(policy.witnesses[j].key.k), policy.witnesses[j].key.k);
	  fprintf (stderr, "hash %u ", j);
	  dump_hex (stderr, sizeof(policy.witnesses[j].hash.h), policy.witnesses[j].hash.h);
	}
      fprintf (stderr, "quorum: ");
      dump_hex (stderr, policy.quorum_length, policy.quorum_bytecode);
    }
  if (policy.num_logs != exp->num_logs)
    die ("Policy %s: unexpected # logs, got %zu", desc, policy.num_logs);
  if (policy.num_witnesses != exp->num_witnesses)
    die ("Policy %s: unexpected # witnesses, got %zu", desc, policy.num_witnesses);

  if (policy.quorum_length != exp->quorum_length
      || memcmp (policy.quorum_bytecode, exp->quorum_bytecode, exp->quorum_length) != 0)
    die ("Policy %s: unexpected quorum byte code", desc);

  if (policy.quorum_stack_size != exp->quorum_stack_size)
    die ("Policy %s: unexpected quorum stack size %zu", desc, policy.quorum_stack_size);

  sigsum_policy_clear (&policy);
}

static void
test_basic_policies (bool verbose)
{
  LITERAL_STATIC const struct {
    const char *desc;
    const char *input;
    struct expect exp;
  } valid[] = {
    { "quorum none policy",
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa http://sigsum.example.org\n"
      "quorum none\n",
      { 1, 0, QUORUM(1, 0x02), 1 } },
    { "single-element groups",
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "witness W bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n"
      "group G1 1 W\n"
      "group G2 all G1\n"
      "group G3 any G2\n"
      "quorum G3",
      { 1, 1, QUORUM(1, 0x40), 1 } },
    { "example policy",
      "# example config\n"
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa http://sigsum.example.org\n"
      "  # comment\n"
      "  log bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n"
      "\n"
      "witness W1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc http://w1\n"
      "# same key for log and key is undesirable, but not an error\n"
      "witness W2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "witness W3 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd http://w3\n"
      "witness W4 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee https://w4/\n"
      "witness W5 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff https://w5\n"
      "\n"
      "group G1 any W1 W2\n"
      "group G2 2 W3 W4 W5\n"
      "group G3 all G1 G2\n"
      "\n"
      "quorum G3\n"
      "\n"
      "  log cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "witness W6 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
      /* Note: no final newline above. */,
      /* Witnesses are sorted as W3, W6, W4, W5, W1, W2. */
      {3, 6, QUORUM(12, 0x44, 0x45, 0x01, 0x81, /* G1 */
		    0x40, 0x42, 0x01, 0x43, 0x01, 0x82, /* G2 */
		    0x01, 0x82, /* G3 */), 3 } },
  };

  for (unsigned i = 0; i < sizeof (valid) / sizeof (valid[0]); i++)
    test_one (valid[i].desc, verbose, strlen(valid[i].input), valid[i].input, &valid[i].exp);
}

static void
test_published_policies (bool verbose)
{
  struct {
    const char *input_file;
    struct expect exp;
  } valid[] = {
    { "sigsum-generic-2025-1.builtin-policy",
      {2, 3, QUORUM(6, 0x40, 0x41, 0x01, 0x42, 0x01, 0x82), 2},
    },
    { "sigsum-test1-2025.builtin-policy",
      {1, 3, QUORUM(6, 0x40, 0x41, 0x01, 0x42, 0x01, 0x82), 2},
    },
    { "sigsum-test2-2025.builtin-policy",
      /* The group of glasklar witnesses get indices 1, 2, 4 */
      {1, 7, QUORUM(15, 0x40, 0x43, 0x01, 0x45, 0x01, 0x46, 0x01,
		    0x41, 0x42, 0x01, 0x44, 0x01, 0x82, /* 2-of-3 group */
		    0x01, 0x84 /* End of 4-of-5 group */), 3},
    },
  };

  for (unsigned i = 0; i < sizeof (valid) / sizeof (valid[0]); i++)
    {
      char *data;
      size_t size = read_src_file (valid[i].input_file, &data);

      test_one (valid[i].input_file, verbose, size, data, &valid[i].exp);

      free (data);
    }
}

static void
test_invalid_policies (void)
{
  struct {
    const char *desc;
    const char *err;
    unsigned lineno;
    const char *input;
  } invalid[] = {
    { "empty", "missing quorum", 0, "" },
    { "invalid control char (formfeed)", "invalid control character", 1,
      "# \f\nquorum none\n" },
    { "invalid control char (DEL)", "invalid control character", 1,
      "# \x7F\nquorum none\n" },
    { "duplicate log", "duplicate log key", 3,
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "quorum none\n" },
    { "end-of-line comment", "bad log line", 1,
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa http://example.org #comment\n" },
    { "empty group", "bad group line", 1,
      "group G all\n" },
    { "duplicate witness", "duplicate witness key", 3,
      "witness W1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "witness W2 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "quorum none\n" },
    { "duplicate name", "duplicate witness name", 2,
      "witness W1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "witness W1 dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\n" },
    { "duplicate none", "duplicate witness name", 1,
      "witness none cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n" },
    { "undef name", "unknown group member", 3,
      "witness W1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "witness W2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "group G all W1 W3 W2\n" },
    { "missing quorum", "missing quorum", 3,
      "witness W1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "witness W2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "group G all W1 W2\n" },
    { "repeated member", "already a member", 3,
      "witness W1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "witness W2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "group G all W1 W2 W1\n"
      "quorum G\n" },
    { "member in two groups", "already a member", 5,
      "witness W1 cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\n"
      "witness W2 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "witness W3 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n"
      "group G1 all W1 W2\n"
      "group G2 all W3 W2\n" },
    {
      "none as member", "invalid none member", 3,
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "witness W bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n"
      "group G all none W\n"
      "quorum G\n" },
    { "self-reference group", "unknown group member", 3,
      "log aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"
      "witness W bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n"
      "group G any G W\n"
      "quorum G\n" },
  };
  for (unsigned i = 0; i < sizeof (invalid) / sizeof (invalid[0]); i++)
    {
      struct sigsum_policy policy;
      struct sigsum_error err;
      if (sigsum_policy_parse (&policy, strlen(invalid[i].input), invalid[i].input, &err))
	die ("invalid policy %d (%s) was accepted", i, invalid[i].desc);
      if (!strstr (err.msg, invalid[i].err))
	die ("unexpected error for invalid policy %d (%s:%zu): %s",
	     i, invalid[i].desc, err.line, err.msg);
      if (err.line != invalid[i].lineno)
	die ("unexpected error lineno for invalid policy %d (%s:%zu): %s",
	     i, invalid[i].desc, err.line, err.msg);
    }
}

int
main (int argc, char **argv)
{
  bool verbose = (argc == 2 && strcmp (argv[1], "-v") == 0);
  test_basic_policies (verbose);
  test_published_policies (verbose);
  test_invalid_policies ();

  return 0;
}
