import * as github from "@actions/github";
import test from "ava";

import {
  fixCodeQualityCategory,
  getPullRequestBranches,
  isAnalyzingPullRequest,
  isDefaultSetup,
  isDynamicWorkflow,
} from "./actions-util";
import { computeAutomationID } from "./api-client";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import { setupTests } from "./testing-utils";
import { initializeEnvironment } from "./util";

setupTests(test);

function withMockedContext<T>(mockPayload: any, testFn: () => T): T {
  const originalPayload = github.context.payload;
  github.context.payload = mockPayload;
  try {
    return testFn();
  } finally {
    github.context.payload = originalPayload;
  }
}

function withMockedEnv<T>(
  envVars: Record<string, string | undefined>,
  testFn: () => T,
): T {
  const originalEnv = { ...process.env };

  // Apply environment changes
  for (const [key, value] of Object.entries(envVars)) {
    if (value === undefined) {
      delete process.env[key];
    } else {
      process.env[key] = value;
    }
  }

  try {
    return testFn();
  } finally {
    // Restore original environment
    process.env = originalEnv;
  }
}

test("computeAutomationID()", async (t) => {
  let actualAutomationID = computeAutomationID(
    ".github/workflows/codeql-analysis.yml:analyze",
    '{"language": "javascript", "os": "linux"}',
  );
  t.deepEqual(
    actualAutomationID,
    ".github/workflows/codeql-analysis.yml:analyze/language:javascript/os:linux/",
  );

  // check the environment sorting
  actualAutomationID = computeAutomationID(
    ".github/workflows/codeql-analysis.yml:analyze",
    '{"os": "linux", "language": "javascript"}',
  );
  t.deepEqual(
    actualAutomationID,
    ".github/workflows/codeql-analysis.yml:analyze/language:javascript/os:linux/",
  );

  // check that an empty environment produces the right results
  actualAutomationID = computeAutomationID(
    ".github/workflows/codeql-analysis.yml:analyze",
    "{}",
  );
  t.deepEqual(
    actualAutomationID,
    ".github/workflows/codeql-analysis.yml:analyze/",
  );

  // check non string environment values
  actualAutomationID = computeAutomationID(
    ".github/workflows/codeql-analysis.yml:analyze",
    '{"number": 1, "object": {"language": "javascript"}}',
  );
  t.deepEqual(
    actualAutomationID,
    ".github/workflows/codeql-analysis.yml:analyze/number:/object:/",
  );

  // check undefined environment
  actualAutomationID = computeAutomationID(
    ".github/workflows/codeql-analysis.yml:analyze",
    undefined,
  );
  t.deepEqual(
    actualAutomationID,
    ".github/workflows/codeql-analysis.yml:analyze/",
  );
});

test.serial("getPullRequestBranches() with pull request context", (t) => {
  withMockedContext(
    {
      pull_request: {
        number: 123,
        base: { ref: "main" },
        head: { label: "user:feature-branch" },
      },
    },
    () => {
      t.deepEqual(getPullRequestBranches(), {
        base: "main",
        head: "user:feature-branch",
      });
      t.is(isAnalyzingPullRequest(), true);
    },
  );
});

test.serial(
  "getPullRequestBranches() returns undefined with push context",
  (t) => {
    withMockedContext(
      {
        push: {
          ref: "refs/heads/main",
        },
      },
      () => {
        t.is(getPullRequestBranches(), undefined);
        t.is(isAnalyzingPullRequest(), false);
      },
    );
  },
);

test.serial(
  "getPullRequestBranches() with Default Setup environment variables",
  (t) => {
    withMockedContext({}, () => {
      withMockedEnv(
        {
          CODE_SCANNING_REF: "refs/heads/feature-branch",
          CODE_SCANNING_BASE_BRANCH: "main",
        },
        () => {
          t.deepEqual(getPullRequestBranches(), {
            base: "main",
            head: "refs/heads/feature-branch",
          });
          t.is(isAnalyzingPullRequest(), true);
        },
      );
    });
  },
);

test.serial(
  "getPullRequestBranches() returns undefined when only CODE_SCANNING_REF is set",
  (t) => {
    withMockedContext({}, () => {
      withMockedEnv(
        {
          CODE_SCANNING_REF: "refs/heads/feature-branch",
          CODE_SCANNING_BASE_BRANCH: undefined,
        },
        () => {
          t.is(getPullRequestBranches(), undefined);
          t.is(isAnalyzingPullRequest(), false);
        },
      );
    });
  },
);

test.serial(
  "getPullRequestBranches() returns undefined when only CODE_SCANNING_BASE_BRANCH is set",
  (t) => {
    withMockedContext({}, () => {
      withMockedEnv(
        {
          CODE_SCANNING_REF: undefined,
          CODE_SCANNING_BASE_BRANCH: "main",
        },
        () => {
          t.is(getPullRequestBranches(), undefined);
          t.is(isAnalyzingPullRequest(), false);
        },
      );
    });
  },
);

test.serial(
  "getPullRequestBranches() returns undefined when no PR context",
  (t) => {
    withMockedContext({}, () => {
      withMockedEnv(
        {
          CODE_SCANNING_REF: undefined,
          CODE_SCANNING_BASE_BRANCH: undefined,
        },
        () => {
          t.is(getPullRequestBranches(), undefined);
          t.is(isAnalyzingPullRequest(), false);
        },
      );
    });
  },
);

test.serial("initializeEnvironment", (t) => {
  initializeEnvironment("1.2.3");
  t.deepEqual(process.env[EnvVar.VERSION], "1.2.3");
});

test.serial("fixCodeQualityCategory", (t) => {
  withMockedEnv(
    {
      GITHUB_EVENT_NAME: "dynamic",
    },
    () => {
      const logger = getRunnerLogger(true);

      // Categories that should get adjusted.
      t.is(fixCodeQualityCategory(logger, "/language:c#"), "/language:csharp");
      t.is(fixCodeQualityCategory(logger, "/language:cpp"), "/language:c-cpp");
      t.is(fixCodeQualityCategory(logger, "/language:c"), "/language:c-cpp");
      t.is(
        fixCodeQualityCategory(logger, "/language:java"),
        "/language:java-kotlin",
      );
      t.is(
        fixCodeQualityCategory(logger, "/language:javascript"),
        "/language:javascript-typescript",
      );
      t.is(
        fixCodeQualityCategory(logger, "/language:typescript"),
        "/language:javascript-typescript",
      );
      t.is(
        fixCodeQualityCategory(logger, "/language:kotlin"),
        "/language:java-kotlin",
      );

      // Categories that should not get adjusted.
      t.is(
        fixCodeQualityCategory(logger, "/language:csharp"),
        "/language:csharp",
      );
      t.is(fixCodeQualityCategory(logger, "/language:go"), "/language:go");
      t.is(
        fixCodeQualityCategory(logger, "/language:actions"),
        "/language:actions",
      );

      // Other cases.
      t.is(fixCodeQualityCategory(logger, undefined), undefined);
      t.is(fixCodeQualityCategory(logger, "random string"), "random string");
      t.is(fixCodeQualityCategory(logger, "kotlin"), "kotlin");
    },
  );
});

test.serial(
  "isDynamicWorkflow() returns true if event name is `dynamic`",
  (t) => {
    process.env.GITHUB_EVENT_NAME = "dynamic";
    t.assert(isDynamicWorkflow());
    process.env.GITHUB_EVENT_NAME = "push";
    t.false(isDynamicWorkflow());
  },
);

test.serial("isDefaultSetup() returns true when expected", (t) => {
  process.env.GITHUB_EVENT_NAME = "dynamic";
  process.env[EnvVar.ANALYSIS_KEY] = "dynamic/github-code-scanning";
  t.assert(isDefaultSetup());
});
