import fs, { readdirSync } from "fs";
import path from "path";
import { migrateTestAction } from "./migrate-test-action.js";
import { showError, showInfo } from "../helpers/common/message-helper.js";
import { parse } from "yaml";
import { Test } from "@apic/api-model/test/Test.js";
import {
  MIGRATION_COMPLETED,
  MIGRATION_STARTED,
} from "../constants/message-constants.js";

beforeEach(() => {
  // @ts-ignore
  mockExit = jest.spyOn(process, "exit").mockImplementation(() => {});
});
jest.mock("../helpers/common/message-helper.js", () => ({
  showError: jest.fn(),
  showInfo: jest.fn(),
}));

describe("MigrateTestAction", () => {
  const namespace = "default";
  describe("read files from localDir", () => {
    const fixtures = path.join(__dirname, "fixtures");
    const outputFixtures = path.join(__dirname, "out-fixtures");

    beforeAll(() => {
      fs.mkdirSync(fixtures, { recursive: true });

      // Valid YAML files
      fs.writeFileSync(path.join(fixtures, "valid.yaml"), "key: value");
      fs.writeFileSync(path.join(fixtures, "valid.yml"), "a: 1");

      // Invalid YAML file
      fs.writeFileSync(
        path.join(fixtures, "bad.yaml"),
        "foo: [this is not valid yaml"
      );

      // Non-YAML file
      fs.writeFileSync(path.join(fixtures, "ignore.txt"), "should be ignored");
    });

    afterAll(() => {
      fs.rmSync(fixtures, { recursive: true, force: true });
      fs.rmSync(outputFixtures, { recursive: true, force: true });
    });

    it("reads a valid YAML file path, but invalid ATM test", async () => {
      const file = path.join(fixtures, "valid.yaml");
      await migrateTestAction({
        localDir: file,
        output: outputFixtures,
        debug: true,
      });
      expect(showInfo).toHaveBeenCalledWith(
        expect.stringContaining("Successfully read: 0 Atm test files")
      );
      expect(showError).toHaveBeenCalledWith(
        expect.stringContaining("Failed to read YAML file:")
      );
    });

    it("returns empty for non-YAML file path", async () => {
      const file = path.join(fixtures, "ignore.txt");
      await migrateTestAction({
        localDir: file,
        output: outputFixtures,
        debug: false,
      });
      expect(showError).toHaveBeenCalledWith(
        expect.stringContaining("Unsupported path type:")
      );
    });

    it("reads all YAML files in a directory, skipping bad and non-ATM files", async () => {
      await migrateTestAction({
        localDir: fixtures,
        output: outputFixtures,
        debug: true,
      });
      expect(showInfo).toHaveBeenCalledWith(
        "Successfully read: 0 Atm test files"
      );
    });

    it("returns 0 files for unreadable YAML file", async () => {
      const bad = path.join(fixtures, "bad.yaml");
      await migrateTestAction({
        localDir: bad,
        output: outputFixtures,
        debug: true,
      });
      expect(showInfo).toHaveBeenCalledWith(
        expect.stringContaining("Successfully read: 0 Atm test files")
      );
    });

    it("warns for unsupported path type", async () => {
      const socketPath = path.join(fixtures, "nonexistent.sock");

      // Create a broken symlink to simulate non-regular file
      try {
        fs.symlinkSync("/dev/null", socketPath);
      } catch {}

      await migrateTestAction({
        localDir: socketPath,
        output: outputFixtures,
        debug: true,
      });
      expect(showError).toHaveBeenCalledWith(
        expect.stringContaining("Unsupported path type:")
      );
    });
  });

  describe("debug mode", () => {
    beforeEach(() => {
      (showInfo as jest.Mock).mockClear();
    });

    it("should print debug messages when debug is true", async () => {
      await migrateTestAction({
        localDir: "",
        output: "",
        debug: true,
      });
      expect(showInfo).toHaveBeenCalledTimes(3);
      expect(showInfo).toHaveBeenCalledWith(
        expect.stringContaining(MIGRATION_STARTED)
      );
      expect(showInfo).toHaveBeenCalledWith(
        expect.stringContaining("Started reading ATM test files from: ")
      );
      expect(showError).toHaveBeenCalledWith(
        expect.stringContaining("Error: ENOENT: no such file or directory,")
      );
    });

    it("should not print debug messages when debug is false", async () => {
      await migrateTestAction({
        localDir: "",
        output: "",
        debug: false,
      });
      expect(showInfo).toHaveBeenCalledTimes(2);
      expect(showInfo).toHaveBeenCalledWith(
        expect.stringContaining(MIGRATION_STARTED)
      );
      expect(showInfo).toHaveBeenCalledWith(
        expect.stringContaining(MIGRATION_COMPLETED)
      );
      expect(showError).toHaveBeenCalledWith(
        expect.stringContaining("Error: ENOENT: no such file or directory,")
      );
    });
  });

  describe("migrateTestAction", () => {
    const output = "test/resources/atm/test-output";
    afterEach(() => {
      fs.rmSync(output, { recursive: true, force: true });
    });

    it("should write valid yaml files for version 2, each for input variables", async () => {
      const inputFileName = "atm-test-v2.yaml";
      const localDir = `test/resources/atm/${inputFileName}`;
      await migrateTestAction({
        localDir,
        output,
        debug: false,
      });

      const files = readdirSync(output, { withFileTypes: true }).filter((f) =>
        f.isFile()
      );
      expect(files.length).toBe(5);

      const test = parse(fs.readFileSync(`${output}/${inputFileName}`, "utf8"));
      expect(test).toEqual(
        expect.objectContaining({
          apiVersion: "api.webmethods.io/beta",
          kind: "test",
          metadata: {
            name: "Test for atm-test-v2",
            namespace: "default",
            tags: [],
            version: "1.0.0",
          },
          spec: {
            api: {
              $endpoint: "// Replace with a valid endpoint",
            },
            environment: {
              $ref: "default:environment_default_atm-test-v2:1.0.0",
            },
            request: [
              {
                assertions: {
                  $ref: "default:assertion_atm-test-v2:1.0.0",
                },
                endpoint: "${base_url}/bank",
                headers: [
                  {
                    key: "Authorization",
                    value: "Bearer ${api_key}",
                  },
                ],
                method: "get",
                resource: "/${map1.path}",
                var: "branches",
              },
              {
                assertions: {
                  $ref: "default:assertion_1_atm-test-v2:1.0.0",
                },
                endpoint:
                  "https://sample-api.us-east-a.apiconnect.automation.ibm.com/orders/order",
                method: "get",
                resource: "/AB1234",
                var: "payload",
              },
            ],
          },
        })
      );

      expect(
        parse(
          fs.readFileSync(
            `${output}/environment_default_atm-test-v2.yaml`,
            "utf-8"
          )
        )
      ).toEqual(
        expect.objectContaining({
          apiVersion: "api.webmethods.io/beta",
          kind: "environment",
          metadata: {
            name: "environment_default_atm-test-v2",
            namespace: "default",
            tags: [],
            version: "1.0.0",
          },
          spec: {
            variables: [
              {
                isSecret: false,
                key: "X-IBM-Client-Id",
                value: "X-IBM-Client-Id",
              },
              {
                isSecret: false,
                key: "orderNumber",
                value: "3",
              },
              {
                isSecret: false,
                key: "base_url",
                value: "url.com",
              },
              {
                isSecret: false,
                key: "api_key",
                value: "abcd",
              },
              {
                isSecret: false,
                key: "value",
                value: 2,
              },
              {
                isSecret: false,
                key: "map1",
                value: {
                  path: "branches2",
                  path1: "branches1",
                },
              },
              {
                isSecret: false,
                key: "boolean",
                value: true,
              },
              {
                isSecret: false,
                key: "array",
                value: ["array1", "array4", "array3"],
              },
            ],
          },
        })
      );
      expect(
        fs.existsSync(`${output}/environment_second_atm-test-v2.yaml`)
      ).toBeTruthy();
      expect(
        fs.existsSync(`${output}/assertion_atm-test-v2.yaml`)
      ).toBeTruthy();
      expect(
        parse(
          fs.readFileSync(`${output}/assertion_1_atm-test-v2.yaml`, "utf-8")
        )
      ).toEqual(
        expect.objectContaining({
          apiVersion: "api.webmethods.io/beta",
          kind: "assertion",
          metadata: {
            name: "assertion_1_atm-test-v2",
            namespace: "default",
            tags: [],
            version: "1.0.0",
          },
          spec: [
            {
              action: "greaterThan",
              key: "${code}",
              name: "assert-greater => greaterThan",
              value: "${value}",
            },
            {
              action: "lessThan",
              key: "${code}",
              name: "assert-less => lessThan",
              value: "${value}",
            },
            {
              action: "equals",
              key: "headers().content-type",
              name: "assert-equals => equals",
              value: "application/json",
            },
            {
              action: "include",
              key: ["application/json", "${array.2}"],
              name: "assert-in => include",
              value: "headers().content-type",
            },
            {
              action: "include",
              key: "headers().content-type",
              name: "assert-contains => include",
              value: "app",
            },
            {
              action: "matches",
              key: "headers().content-type",
              name: "assert-matches => matches",
              value: "^[a-z]+/json",
            },
            {
              action: "haveProperty",
              key: "${payload}",
              name: "assert-exists => haveProperty",
            },
            {
              action: "type",
              key: "${payload.tracking_reference}",
              name: "assert-is => type",
              value: "string",
            },
            {
              action: "haveProperty",
              key: "${payload.shipped_at}",
              name: "assert-exists => haveProperty",
            },
          ],
        })
      );
    });

    it("should write valid yaml file for version 1", async () => {
      const inputFileName = "atm-test-v1.yaml";
      const localDir = `test/resources/atm/${inputFileName}`;
      await migrateTestAction({
        localDir,
        output,
        debug: false,
      });
      const files = readdirSync(output, { withFileTypes: true })
        .filter((f) => f.isFile())
        .map(({ name }) => name);
      expect(files.length).toBe(4);
      expect(files).toEqual(
        expect.arrayContaining([
          "assertion_1_atm-test-v1.yaml",
          "assertion_atm-test-v1.yaml",
          "atm-test-v1.yaml",
          "environment_default_atm-test-v1.yaml",
        ])
      );
    });

    it("should write valid yaml for if condition in atm yaml's", async () => {
      const inputFileName = "atm-test-if-v2.yaml";
      const localDir = `test/resources/atm/${inputFileName}`;
      await migrateTestAction({
        localDir,
        output,
        debug: false,
      });
      expect(
        parse(fs.readFileSync(`${output}/assertion_${inputFileName}`, "utf8"))
      ).toEqual(
        expect.objectContaining({
          spec: [
            {
              action: "haveProperty",
              if: "${branches}",
              key: "${branches.1}",
              name: "assert-exists => haveProperty",
            },
            {
              action: "equals",
              if: "${branches}",
              key: "${branches.0.type}",
              name: "assert-equals => equals",
              stopOnFail: true,
              value: "string",
            },
            {
              action: "equals",
              key: "${branches.0.type}",
              name: "assert-compares => equals",
              stopOnFail: true,
              value: "${branches.1.type}",
            },
          ],
        })
      );
    });

    it("should read and write valid petStore yaml", async () => {
      const inputFileName = "atm-pet-store.yaml";
      const localDir = `test/resources/atm/${inputFileName}`;
      const outputV1 = "test/resources/atm/out";
      fs.rmSync(outputV1, { recursive: true, force: true });
      await migrateTestAction({
        localDir,
        output: outputV1,
        debug: true,
      });
      expect(fs.existsSync(`${outputV1}/${inputFileName}`)).toBeTruthy();
    });

    it("should read and output all atm tests", async () => {
      const localDir = "test/resources/atm/";

      await migrateTestAction({
        localDir,
        output,
        debug: false,
      });

      const files = readdirSync(output, { withFileTypes: true })
        .filter((f) => f.isFile())
        .map(({ name }) => name);

      expect(files.length).toBe(20);
      expect(files).toEqual(
        expect.arrayContaining([
          "assertion_1_atm-pet-store.yaml",
          "assertion_1_atm-test-if-v2.yaml",
          "assertion_1_atm-test-v1.yaml",
          "assertion_1_atm-test-v2.yaml",
          "assertion_atm-pet-store.yaml",
          "assertion_atm-test-if-v2.yaml",
          "assertion_atm-test-v1.yaml",
          "assertion_atm-test-v2.yaml",
          "atm-pet-store.yaml",
          "atm-test-if-v2.yaml",
        ])
      );
    });

    it("should process atm files with invalid type also", async () => {
      const inputFileName = "atm-test-invalid-type";
      const localDir = `test/resources/atm/${inputFileName}.yml`;
      await migrateTestAction({
        localDir,
        output,
        debug: false,
      });
      expect(
        parse(
          fs.readFileSync(`${output}/assertion_${inputFileName}.yaml`, "utf8")
        )
      ).toEqual(
        expect.objectContaining({
          spec: [
            {
              action: "type",
              key: "${branches}",
              name: "assert-is => type",
              value: "array",
            },
          ],
        })
      );
    });
  });
});
