import { describe, it, expect } from "vitest";
import { parseWithSchema } from "../src/json";
import { z } from "zod";

describe("json", () => {
  describe("parseWithSchema", () => {
    const schema = z.object({
      test: z.boolean().optional(),
      name: z.string(),
      nested: z.object({ foo: z.string() }).strict().optional(),
    });
    it("should throw with invalid json", () => {
      expect(() => parseWithSchema("blah", schema)).toThrow(
        new Error(
          `Invalid JSON: Unexpected token 'b', "blah" is not valid JSON`,
        ),
      );
    });
    it("should parse", () => {
      const json = `{
        "test":true,
        "name":"bob",
        "unknown":"unknown",
        "nested":{
            "foo":"bar"
        }
      }`;

      expect(parseWithSchema(json, schema)).toStrictEqual({
        test: true,
        name: "bob",
        nested: { foo: "bar" },
      });
    });
    it("should throw with invalid schema", () => {
      const json = `{
            "test":"yes",
            "nested":{
                "foo":1
            }
          }`;

      expect(() => parseWithSchema(json, schema)).toThrow(
        new Error(
          `JSON does not match schema: "test" expected boolean, received string, "name" required, "nested.foo" expected string, received number`,
        ),
      );
    });
    it("should migrate if valid json", () => {
      const json = `{
          "name": 1
          }`;

      const result = parseWithSchema(json, schema, {
        migrate: () => {
          return {
            name: "migrated",
            test: false,
          };
        },
      });

      expect(result).toStrictEqual({
        name: "migrated",
        test: false,
      });
    });
    it("should revert to fallback if invalid json", () => {
      const json = `blah`;

      const result = parseWithSchema(json, schema, {
        fallback: {
          name: "migrated",
          test: false,
        },
      });

      expect(result).toStrictEqual({
        name: "migrated",
        test: false,
      });
    });
    it("should throw if migration fails", () => {
      const json = `{
          "name": 1
          }`;

      expect(() => {
        parseWithSchema(json, schema, {
          //@ts-expect-error need to test migration failure
          migrate: () => {
            return {
              name: null,
              test: "Hi",
            };
          },
        });
      }).toThrow(
        new Error(
          `Migrated value does not match schema: "test" expected boolean, received string, "name" expected string, received null`,
        ),
      );
    });
    it("should revert to fallback if migration fails", () => {
      const json = `{
          "name": 1
          }`;

      const result = parseWithSchema(json, schema, {
        fallback: {
          name: "fallback",
          test: false,
        },
        //@ts-expect-error need to test migration failure
        migrate: () => {
          return {
            name: null,
            test: "Hi",
          };
        },
      });

      expect(result).toStrictEqual({
        name: "fallback",
        test: false,
      });
    });
    it("migrate function should receive value", () => {
      const json = `{
        "test":"test"
      }`;

      const result = parseWithSchema(json, schema, {
        migrate: (value) => {
          expect(value).toEqual({ test: "test" });
          return {
            name: "valid",
          };
        },
      });

      expect(result).toStrictEqual({
        name: "valid",
      });
    });
  });
});
