import { setupTestFixtures } from "@turbo/test-utils";
import fs from "fs-extra";
import { transformer } from "../src/transforms/create-turbo-config";

describe("create-turbo-config", () => {
  const { useFixture } = setupTestFixtures({
    directory: __dirname,
    test: "create-turbo-config",
  });

  test("package.json config exists but no turbo.json config - basic", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-turbo-json-config" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // get config from package.json for comparison later
    const turboConfig = JSON.parse(read("package.json") || "{}").turbo;
    expect(turboConfig).toBeDefined();
    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });

    // turbo.json should now exist (and match the package.json config)
    expect(JSON.parse(read("turbo.json") || "{}")).toEqual(turboConfig);

    // result should be correct
    expect(result.fatalError).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "modified",
          "additions": 0,
          "deletions": 1,
        },
        "turbo.json": Object {
          "action": "modified",
          "additions": 1,
          "deletions": 0,
        },
      }
    `);
  });

  test("package.json config exists but no turbo.json config - repeat run", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-turbo-json-config" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // get config from package.json for comparison later
    const turboConfig = JSON.parse(read("package.json") || "{}").turbo;
    expect(turboConfig).toBeDefined();
    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });

    // turbo.json should now exist (and match the package.json config)
    expect(JSON.parse(read("turbo.json") || "{}")).toEqual(turboConfig);

    // result should be correct
    expect(result.fatalError).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "modified",
          "additions": 0,
          "deletions": 1,
        },
        "turbo.json": Object {
          "action": "modified",
          "additions": 1,
          "deletions": 0,
        },
      }
    `);

    // run the transformer
    const repeatResult = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });
    // result should be correct
    expect(repeatResult.fatalError).toBeUndefined();
    expect(repeatResult.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
        "turbo.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
      }
    `);
  });

  test("package.json config exists but no turbo.json config - dry", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-turbo-json-config" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // get config from package.json for comparison later
    const turboConfig = JSON.parse(read("package.json") || "{}").turbo;
    expect(turboConfig).toBeDefined();
    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: true, print: false },
    });

    // turbo.json still not exist (dry run)
    expect(read("turbo.json")).toBeUndefined();

    // result should be correct
    expect(result.fatalError).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "skipped",
          "additions": 0,
          "deletions": 1,
        },
        "turbo.json": Object {
          "action": "skipped",
          "additions": 1,
          "deletions": 0,
        },
      }
    `);
  });

  test("package.json config exists but no turbo.json config - print", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-turbo-json-config" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // get config from package.json for comparison later
    const turboConfig = JSON.parse(read("package.json") || "{}").turbo;
    expect(turboConfig).toBeDefined();
    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: true },
    });

    // turbo.json should now exist (and match the package.json config)
    expect(JSON.parse(read("turbo.json") || "{}")).toEqual(turboConfig);

    // result should be correct
    expect(result.fatalError).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "modified",
          "additions": 0,
          "deletions": 1,
        },
        "turbo.json": Object {
          "action": "modified",
          "additions": 1,
          "deletions": 0,
        },
      }
    `);
  });

  test("package.json config exists but no turbo.json config - dry & print", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-turbo-json-config" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // get config from package.json for comparison later
    const turboConfig = JSON.parse(read("package.json") || "{}").turbo;
    expect(turboConfig).toBeDefined();
    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: true, print: true },
    });

    // turbo.json still not exist (dry run)
    expect(read("turbo.json")).toBeUndefined();

    // result should be correct
    expect(result.fatalError).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "skipped",
          "additions": 0,
          "deletions": 1,
        },
        "turbo.json": Object {
          "action": "skipped",
          "additions": 1,
          "deletions": 0,
        },
      }
    `);
  });

  test("no package.json config or turbo.json file exists", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-package-json-config" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // get config from package.json for comparison later
    const packageJsonConfig = JSON.parse(read("package.json") || "{}");
    const turboConfig = packageJsonConfig.turbo;
    expect(turboConfig).toBeUndefined();
    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });

    // turbo.json should still not exist
    expect(read("turbo.json")).toBeUndefined();

    // make sure we didn't change the package.json
    expect(JSON.parse(read("package.json") || "{}")).toEqual(packageJsonConfig);

    // result should be correct
    expect(result.fatalError).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
        "turbo.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
      }
    `);
  });

  test("no package.json file exists", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-package-json-file" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });

    // turbo.json should still not exist
    expect(read("turbo.json")).toBeUndefined();

    // result should be correct
    expect(result.fatalError?.message).toMatch(
      /No package\.json found at .*?\. Is the path correct\?/
    );
  });

  test("turbo.json file exists and no package.json config exists", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "turbo-json-config" });

    // turbo.json should exist
    expect(read("turbo.json")).toBeDefined();

    // no config should exist in package.json
    const packageJsonConfig = JSON.parse(read("package.json") || "{}");
    const turboConfig = packageJsonConfig.turbo;
    expect(turboConfig).toBeUndefined();

    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });

    // turbo.json should still exist
    expect(read("turbo.json")).toBeDefined();

    // make sure we didn't change the package.json
    expect(JSON.parse(read("package.json") || "{}")).toEqual(packageJsonConfig);

    // result should be correct
    expect(result.fatalError).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
        "turbo.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
      }
    `);
  });

  test("turbo.json file exists and package.json config exists", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "both-configs" });

    // turbo.json should exist
    const turboJsonConfig = JSON.parse(read("turbo.json") || "{}");
    expect(turboJsonConfig.pipeline).toBeDefined();

    // no config should exist in package.json
    const packageJsonConfig = JSON.parse(read("package.json") || "{}");
    const turboConfig = JSON.parse(read("package.json") || "{}").turbo;
    expect(turboConfig).toBeDefined();

    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });

    // make sure we didn't change the package.json
    expect(JSON.parse(read("package.json") || "{}")).toEqual(packageJsonConfig);

    // make sure we didn't change the turbo.json
    expect(JSON.parse(read("turbo.json") || "{}")).toEqual(turboJsonConfig);

    // result should be correct
    expect(result.fatalError?.message).toBeUndefined();
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
        "turbo.json": Object {
          "action": "unchanged",
          "additions": 0,
          "deletions": 0,
        },
      }
    `);
  });

  test("errors when unable to write json", () => {
    // load the fixture for the test
    const { root, read } = useFixture({ fixture: "no-turbo-json-config" });

    // turbo.json should not exist
    expect(read("turbo.json")).toBeUndefined();

    // get config from package.json for comparison later
    const turboConfig = JSON.parse(read("package.json") || "{}").turbo;
    expect(turboConfig).toBeDefined();

    const mockWriteJsonSync = jest
      .spyOn(fs, "writeJsonSync")
      .mockImplementation(() => {
        throw new Error("could not write file");
      });

    // run the transformer
    const result = transformer({
      root,
      options: { force: false, dry: false, print: false },
    });

    // turbo.json should still not exist (error writing)
    expect(read("turbo.json")).toBeUndefined();

    // result should be correct
    expect(result.fatalError).toBeDefined();
    expect(result.fatalError?.message).toMatch(
      "Encountered an error while transforming files"
    );
    expect(result.changes).toMatchInlineSnapshot(`
      Object {
        "package.json": Object {
          "action": "error",
          "additions": 0,
          "deletions": 1,
          "error": [Error: could not write file],
        },
        "turbo.json": Object {
          "action": "error",
          "additions": 1,
          "deletions": 0,
          "error": [Error: could not write file],
        },
      }
    `);

    mockWriteJsonSync.mockRestore();
  });
});
