import { assertEquals, assertMatch, assertThrows } from "./test_deps.ts";
import { cryptoRandomString } from "./mod.ts";

// Probabilistic, result is always less than or equal to actual set size, chance it is less is below 1e-256 for sizes up to 32656
const generatedCharacterSetSize = (
  options: { type?: string; characters?: string },
  targetSize: number,
) => {
  const set = new Set();
  const length = targetSize * 640;
  const string = cryptoRandomString({ ...options, length });

  for (let i = 0; i < length; i++) {
    set.add(string[i]);
  }

  return set.size;
};

Deno.test("main", () => {
  assertEquals(cryptoRandomString({ length: 0 }).length, 0);
  assertEquals(cryptoRandomString({ length: 10 }).length, 10);
  assertEquals(cryptoRandomString({ length: 100 }).length, 100);
  assertMatch(cryptoRandomString({ length: 100 }), /^[a-f\d]*$/); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({}, 16), 16);
});

Deno.test("hex", () => {
  assertEquals(cryptoRandomString({ length: 0, type: "hex" }).length, 0);
  assertEquals(cryptoRandomString({ length: 10, type: "hex" }).length, 10);
  assertEquals(cryptoRandomString({ length: 100, type: "hex" }).length, 100);
  assertMatch(cryptoRandomString({ length: 100, type: "hex" }), /^[a-f\d]*$/); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({ type: "hex" }, 16), 16);
});

Deno.test("base64", () => {
  assertEquals(cryptoRandomString({ length: 0, type: "base64" }).length, 0);
  assertEquals(cryptoRandomString({ length: 10, type: "base64" }).length, 10);
  assertEquals(cryptoRandomString({ length: 100, type: "base64" }).length, 100);
  assertMatch(
    cryptoRandomString({ length: 100, type: "base64" }),
    /^[a-zA-Z\d/+]*$/,
  ); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({ type: "base64" }, 64), 64);
});

Deno.test("url-safe", () => {
  assertEquals(cryptoRandomString({ length: 0, type: "url-safe" }).length, 0);
  assertEquals(cryptoRandomString({ length: 10, type: "url-safe" }).length, 10);
  assertEquals(
    cryptoRandomString({ length: 100, type: "url-safe" }).length,
    100,
  );
  assertMatch(
    cryptoRandomString({ length: 100, type: "url-safe" }),
    /^[a-zA-Z\d._~-]*$/,
  ); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({ type: "url-safe" }, 66), 66);
});

Deno.test("numeric", () => {
  assertEquals(cryptoRandomString({ length: 0, type: "numeric" }).length, 0);
  assertEquals(cryptoRandomString({ length: 10, type: "numeric" }).length, 10);
  assertEquals(
    cryptoRandomString({ length: 100, type: "numeric" }).length,
    100,
  );
  assertMatch(cryptoRandomString({ length: 100, type: "numeric" }), /^[\d]*$/); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({ type: "numeric" }, 10), 10);
});

Deno.test("distinguishable", () => {
  assertEquals(
    cryptoRandomString({ length: 0, type: "distinguishable" }).length,
    0,
  );
  assertEquals(
    cryptoRandomString({ length: 10, type: "distinguishable" }).length,
    10,
  );
  assertEquals(
    cryptoRandomString({ length: 100, type: "distinguishable" }).length,
    100,
  );
  assertMatch(
    cryptoRandomString({ length: 100, type: "distinguishable" }),
    /^[CDEHKMPRTUWXY012458]*$/,
  ); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({ type: "distinguishable" }, 19), 19);
});

Deno.test("ascii-printable", () => {
  assertEquals(
    cryptoRandomString({ length: 0, type: "ascii-printable" }).length,
    0,
  );
  assertEquals(
    cryptoRandomString({ length: 10, type: "ascii-printable" }).length,
    10,
  );
  assertEquals(
    cryptoRandomString({ length: 100, type: "ascii-printable" }).length,
    100,
  );
  assertMatch(
    cryptoRandomString({ length: 100, type: "ascii-printable" }),
    /^[!"#$%&'()*+,-./\d:;<=>?@A-Z[\\\]^_`a-z{|}~]*$/,
  ); // Sanity check, probabilistic
});

Deno.test("alphanumeric", () => {
  assertEquals(
    cryptoRandomString({ length: 0, type: "alphanumeric" }).length,
    0,
  );
  assertEquals(
    cryptoRandomString({ length: 10, type: "alphanumeric" }).length,
    10,
  );
  assertEquals(
    cryptoRandomString({ length: 100, type: "alphanumeric" }).length,
    100,
  );
  assertMatch(
    cryptoRandomString({ length: 100, type: "alphanumeric" }),
    /^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]*$/,
  ); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({ type: "alphanumeric" }, 19), 62);
});

Deno.test("characters", () => {
  assertEquals(cryptoRandomString({ length: 0, characters: "1234" }).length, 0);
  assertEquals(
    cryptoRandomString({ length: 10, characters: "1234" }).length,
    10,
  );
  assertEquals(
    cryptoRandomString({ length: 100, characters: "1234" }).length,
    100,
  );
  assertMatch(
    cryptoRandomString({ length: 100, characters: "1234" }),
    /^[1-4]*$/,
  ); // Sanity check, probabilistic
  assertEquals(generatedCharacterSetSize({ characters: "1234" }, 4), 4);
  assertEquals(generatedCharacterSetSize({ characters: "0123456789" }, 10), 10);
});

Deno.test("argument errors", () => {
  assertThrows(() => {
    cryptoRandomString({ length: Infinity });
  });

  assertThrows(() => {
    cryptoRandomString({ length: -1 });
  });

  assertThrows(() => {
    cryptoRandomString({ length: 0, type: "hex", characters: "1234" });
  });

  assertThrows(() => {
    cryptoRandomString({ length: 0, type: "unknown" });
  });
});