var nota = use('nota');
var os = use('os');

// Helper function to convert hex string to ArrayBuffer
function hexToBuffer(hex) {
  let bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < hex.length; i += 2)
    bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
  return bytes.buffer;
}

// Helper function to convert ArrayBuffer to hex string
function bufferToHex(buffer) {
  return Array.from(new Uint8Array(buffer))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('')
    .toLowerCase();
}

var EPSILON = 1e-12

// Deep comparison function for objects and arrays
function deepCompare(expected, actual, path = '') {
  if (expected == actual) return { passed: true, messages: [] };

  if (typeof expected == 'number' && typeof actual == 'number') {
    if (isNaN(expected) && isNaN(actual))
      return { passed: true, messages: [] };
    
    def diff = Math.abs(expected - actual);
    if (diff <= EPSILON)
      return { passed: true, messages: [] };
    
    return {
      passed: false,
      messages: [
        `Value mismatch at ${path}: expected ${expected}, got ${actual}`,
        `Difference of ${diff} is larger than tolerance ${EPSILON}`
      ]
    };
  }

  if (expected instanceof ArrayBuffer && actual instanceof ArrayBuffer) {
    def expArray = Array.from(new Uint8Array(expected));
    def actArray = Array.from(new Uint8Array(actual));
    return deepCompare(expArray, actArray, path);
  }

  if (actual instanceof ArrayBuffer)
    actual = Array.from(new Uint8Array(actual));

  if (Array.isArray(expected) && Array.isArray(actual)) {
    if (expected.length != actual.length)
      return {
        passed: false,
        messages: [`Array length mismatch at ${path}: expected ${expected.length}, got ${actual.length}`]
      };
    let messages = [];
    for (let i = 0; i < expected.length; i++) {
      def result = deepCompare(expected[i], actual[i], `${path}[${i}]`);
      if (!result.passed) messages.push(...result.messages);
    }
    return { passed: messages.length == 0, messages };
  }

  if (typeof expected == 'object' && expected != null && 
      typeof actual == 'object' && actual != null) {
    def expKeys = Object.keys(expected).sort();
    def actKeys = Object.keys(actual).sort();
    if (JSON.stringify(expKeys) != JSON.stringify(actKeys))
      return {
        passed: false,
        messages: [`Object keys mismatch at ${path}: expected ${expKeys}, got ${actKeys}`]
      };
    let messages = [];
    for (let key of expKeys) {
      def result = deepCompare(expected[key], actual[key], `${path}.${key}`);
      if (!result.passed) messages.push(...result.messages);
    }
    return { passed: messages.length == 0, messages };
  }

  return {
    passed: false,
    messages: [`Value mismatch at ${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`]
  };
}

// Extended test cases covering all Nota types from documentation
var testarr = []
var hex = "a374"
for (var i = 0; i < 500; i++) {
  testarr.push(1)
  hex += "61"
}

var testCases = [
  // Integer tests
  { input: 0, expectedHex: "60" },
  { input: 2023, expectedHex: "e08f67" },
  { input: -1, expectedHex: "69" },
  { input: 7, expectedHex: "67" },
  { input: -7, expectedHex: "6f" },
  { input: 1023, expectedHex: "e07f" },
  { input: -1023, expectedHex: "ef7f" },

  // Symbol tests
  { input: null, expectedHex: "70" },
  { input: false, expectedHex: "72" },
  { input: true, expectedHex: "73" },

  // Floating Point tests
  { input: -1.01, expectedHex: "5a65" },
  { input: 98.6, expectedHex: "51875a" },
  { input: -0.5772156649, expectedHex: "d80a95c0b0bd69" },
  { input: -1.00000000000001, expectedHex: "d80e96deb183e98001" },
  { input: -10000000000000, expectedHex: "c80d01" },

  // Text tests
  { input: "", expectedHex: "10" },
  { input: "cat", expectedHex: "13636174" },
  { input: "U+1F4A9 「うんち絵文字」 «💩»", 
    expectedHex: "9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b" },

  // Blob tests
  { input: new Uint8Array([0xFF, 0xAA]).buffer, expectedHex: "8010ffaa" },
  { input: new Uint8Array([0b11110000, 0b11100011, 0b00100000, 0b10000000]).buffer, 
    expectedHex: "8019f0e32080" },

  { input: testarr, expectedHex: hex },

  // Array tests
  { input: [], expectedHex: "20" },
  { input: [1, 2, 3], expectedHex: "23616263" },
  { input: [-1, 0, 1.5], expectedHex: "2369605043" },

  // Record tests
  { input: {}, expectedHex: "30" },
  { input: { a: 1, b: 2 }, expectedHex: "32116161116262" },
  
  // Complex nested structures
  { input: {
      num: 42,
      arr: [1, -1, 2.5],
      str: "test",
      obj: { x: true }
    }, 
    expectedHex: "34216e756d622a2173747214746573742161727223616965235840216f626a21117873" },
  
  // Private prefix test
  { input: { private: { address: "test" } }, 
    expectedHex: "317821616464726573731474657374" },
  
  // System prefix test
  { input: { system: { msg: "hello" } }, 
    expectedHex: "3179216d73671568656c6c6f" },

  { input: [{ system: {msg: "hello" } }, {
      num: 42,
      arr: [1, -1, 2.5],
      str: "test",
      obj: { x: true }
    }], expectedHex: "223179216d73671568656c6c6f34216e756d622a2173747214746573742161727223616965235840216f626a21117873" },

  // Additional edge cases
  { input: new Uint8Array([]).buffer, expectedHex: "00" },
  { input: [[]], expectedHex: "2120" },
  { input: { "": "" }, expectedHex: "311010" },
  { input: 1e-10, expectedHex: "d00a01" },

  // Replacer tests
  { input: { a: 1, b: 2 },
    replacer: (key, value) => typeof value == 'number' ? value * 2 : value,
    expected: { a: 2, b: 4 },
    testType: 'replacer' },
  
  { input: { x: "test", y: 5 },
    replacer: (key, value) => key == 'x' ? value + "!" : value,
    expected: { x: "test!", y: 5 },
    testType: 'replacer' },

  // Reviver tests
  { input: { a: 1, b: 2 },
    reviver: (key, value) => typeof value == 'number' ? value * 3 : value,
    expected: { a: 3, b: 6 },
    testType: 'reviver' },
  
  { input: { x: "test", y: 10 },
    reviver: (key, value) => key == 'y' ? value + 1 : value,
    expected: { x: "test", y: 11 },
    testType: 'reviver' }
];

// Run tests and collect results
let results = [];
let testCount = 0;

for (let test of testCases) {
  testCount++;
  let testName = `Test ${testCount}: ${JSON.stringify(test.input)}${test.testType ? ` (${test.testType})` : ''}`;
  let passed = true;
  let messages = [];

  // Test encoding
  let encoded = test.replacer ? nota.encode(test.input, test.replacer) : nota.encode(test.input);
  if (!(encoded instanceof ArrayBuffer)) {
    passed = false;
    messages.push("Encode should return ArrayBuffer");
  } else {
    if (test.expectedHex) {
      let encodedHex = bufferToHex(encoded);
      if (encodedHex != test.expectedHex.toLowerCase()) {
        messages.push(
          `Hex encoding differs (informational):
          Expected: ${test.expectedHex}
          Got: ${encodedHex}`
        );
      }
    }

    // Test decoding
    let decoded = test.reviver ? nota.decode(encoded, test.reviver) : nota.decode(encoded);
    let expected = test.expected || test.input;

    // Normalize ArrayBuffer and special cases for comparison
    if (expected instanceof ArrayBuffer)
      expected = Array.from(new Uint8Array(expected));
    if (decoded instanceof ArrayBuffer)
      decoded = Array.from(new Uint8Array(decoded));
    if (expected && (expected.private || expected.system)) {
      def key = expected.private ? 'private' : 'system';
      expected = { [key]: expected[key] };
    }

    def compareResult = deepCompare(expected, decoded);
    if (!compareResult.passed) {
      passed = false;
      messages.push("Decoding failed:");
      messages.push(...compareResult.messages);
    }
  }

  results.push({ testName, passed, messages });

  if (!passed) {
    log.console(`\nDetailed Failure Report for ${testName}:`);
    log.console(`Input: ${JSON.stringify(test.input)}`);
    if (test.replacer) log.console(`Replacer: ${test.replacer.toString()}`);
    if (test.reviver) log.console(`Reviver: ${test.reviver.toString()}`);
    log.console(messages.join("\n"));
    log.console("");
  }
}

// Summary
log.console("\nTest Summary:");
results.forEach(result => {
  log.console(`${result.testName} - ${result.passed ? "Passed" : "Failed"}`);
  if (!result.passed)
    log.console(result.messages)
});

let passedCount = results.filter(r => r.passed).length;
log.console(`\nResult: ${passedCount}/${testCount} tests passed`);

if (passedCount < testCount) {
  log.console("Overall: FAILED");
//  os.exit(1);
} else {
  log.console("Overall: PASSED");
//  os.exit(0);
}