/**
 * @jest-environment jsdom
 */

import { base64urlToBuffer } from "./base64url";
import { Schema } from "./schema-format";
import {
  convert,
  convertValue,
  copyValue,
  optional,
  required,
} from "./convert";
import "./arraybuffer.jest";

describe("conversion", () => {
  test("can handle empty schema", () => {
    const schema: Schema = {};
    expect(() => convert(base64urlToBuffer, schema, {})).not.toThrow();
  });

  test("copies a required `copy` value", () => {
    const schema: Schema = { number: { required: true, schema: "copy" } };
    const converted = convert(base64urlToBuffer, schema, { number: 4 });
    expect(converted.number).toBe(4);
  });

  test("errors when a required `copy` value is missing", () => {
    const schema: Schema = { number: { required: true, schema: "copy" } };
    expect(() => convert(base64urlToBuffer, schema, {})).toThrowError(
      /Missing key/,
    );
  });

  test("copies a required `convert` value", () => {
    const schema: Schema = { number: { required: true, schema: "convert" } };
    const converted = convert(base64urlToBuffer, schema, { number: "AA==" });
    expect(converted.number).toEqualBuffer(new Uint8Array([0]));
  });

  test("errors when a required `convert` value is missing", () => {
    const schema: Schema = { number: { required: true, schema: "convert" } };
    expect(() => convert(base64urlToBuffer, schema, {})).toThrowError(
      /Missing key/,
    );
  });

  test("allows a missing optional value", () => {
    const schema: Schema = { number: { required: false, schema: "copy" } };
    const converted = convert(base64urlToBuffer, schema, {});
    expect(converted).not.toHaveProperty("number");
  });

  test("ignores unknown properties", () => {
    const schema: Schema = { number: { required: false, schema: "copy" } };
    const converted = convert(base64urlToBuffer, schema, {
      number: 6,
      extra: "hi",
    });
    expect(converted.number).toBe(6);
    expect(converted).not.toHaveProperty("extra");
  });

  test("converts object", () => {
    const schema: Schema = {
      nestedObject: {
        required: true,
        schema: {
          number: { required: true, schema: "copy" },
          convertField: { required: true, schema: "convert" },
        },
      },
    };
    const converted = convert(base64urlToBuffer, schema, {
      nestedObject: { number: 7, convertField: "BB==" },
    });
    expect(converted.nestedObject.number).toBe(7);
    expect(converted.nestedObject.convertField).toEqualBuffer(
      new Uint8Array([4]),
    );
  });

  test("converts object list", () => {
    const schema: Schema = {
      nestedObjectList: {
        required: true,
        schema: [
          {
            number: { required: true, schema: "copy" },
            string: { required: false, schema: "copy" },
            convertField: { required: true, schema: "convert" },
          },
        ],
      },
    };
    const converted = convert(base64urlToBuffer, schema, {
      nestedObjectList: [
        { number: 8, string: "hi", convertField: "CC==" },
        { number: 9, convertField: "DD==" },
      ],
    });

    expect(converted.nestedObjectList).toHaveLength(2);

    expect(converted.nestedObjectList[0].number).toBe(8);
    expect(converted.nestedObjectList[0].string).toBe("hi");
    expect(converted.nestedObjectList[0].convertField).toEqualBuffer(
      new Uint8Array([8]),
    );

    expect(converted.nestedObjectList[1].number).toBe(9);
    expect(converted.nestedObjectList[1]).not.toHaveProperty("string");
    expect(converted.nestedObjectList[1].convertField).toEqualBuffer(
      new Uint8Array([12]),
    );
  });
});

test("converts leaf lists", () => {
  const schema: Schema = ["convert"];
  const converted = convert(base64urlToBuffer, schema, ["EE", "FF"]);
  expect(converted).toHaveLength(2);
  expect(converted[0]).toEqualBuffer(new Uint8Array([16]));
  expect(converted[1]).toEqualBuffer(new Uint8Array([20]));
});

describe("convenience functions", () => {
  test("can be called", () => {
    const schema: Schema = {
      a: required(copyValue),
      b: required(convertValue),
      c: optional(copyValue),
      d: optional(convertValue),
    };
    const converted = convert(base64urlToBuffer, schema, {
      a: 5,
      b: "EE",
      c: 7,
      d: "FF",
    });
    expect(converted.a).toBe(5);
    expect(converted.b).toEqualBuffer(new Uint8Array([16]));
    expect(converted.c).toBe(7);
    expect(converted.d).toEqualBuffer(new Uint8Array([20]));
  });

  test("enforce required value", () => {
    const schema: Schema = { number: required(copyValue) };
    expect(() => convert(base64urlToBuffer, schema, {})).toThrowError(
      /Missing key/,
    );
  });

  test("allow leaving out optional value", () => {
    const schema: Schema = { number: { required: false, schema: "copy" } };
    const converted = convert(base64urlToBuffer, schema, {});
    expect(converted).not.toHaveProperty("number");
  });
});

describe("conversion function", () => {
  function double(s: string): string {
    return s + s;
  }

  test("allows using an arbitrary conversion function", () => {
    const schema: Schema = {
      convertField: { required: true, schema: "convert" },
    };
    const converted = convert(double, schema, { convertField: "hi" });
    expect(converted.convertField).toBe("hihi");
  });
});
