import * as fs from "fs";
import * as path from "path";

import test from "ava";
import * as sinon from "sinon";

import { CodeQuality, CodeScanning, RiskAssessment } from "./analyses";
import {
  runQueries,
  defaultSuites,
  resolveQuerySuiteAlias,
  addSarifExtension,
  diffRangeExtensionPackContents,
} from "./analyze";
import { createStubCodeQL } from "./codeql";
import { Feature } from "./feature-flags";
import { KnownLanguage } from "./languages";
import { getRunnerLogger } from "./logging";
import {
  setupTests,
  setupActionsVars,
  createFeatures,
  createTestConfig,
} from "./testing-utils";
import * as uploadLib from "./upload-lib";
import * as util from "./util";

setupTests(test);

/**
 * Checks the status report produced by the analyze Action.
 *
 * - Checks that the duration fields are populated for the correct language.
 * - Checks that the QA telemetry status report fields are populated when the QA feature flag is enabled.
 */
test.serial("status report fields", async (t) => {
  return await util.withTmpDir(async (tmpDir) => {
    setupActionsVars(tmpDir, tmpDir);

    const memoryFlag = "";
    const threadsFlag = "";
    sinon.stub(uploadLib, "validateSarifFileSchema");

    for (const language of Object.values(KnownLanguage)) {
      const codeql = createStubCodeQL({
        databaseRunQueries: async () => {},
        databaseInterpretResults: async (
          _db: string,
          _queriesRun: string[],
          sarifFile: string,
        ) => {
          fs.writeFileSync(
            sarifFile,
            JSON.stringify({
              runs: [
                // references a rule with the lines-of-code tag, so baseline should be injected
                {
                  tool: {
                    extensions: [
                      {
                        rules: [
                          {
                            properties: {
                              tags: ["lines-of-code"],
                            },
                          },
                        ],
                      },
                    ],
                  },
                  properties: {
                    metricResults: [
                      {
                        rule: {
                          index: 0,
                          toolComponent: {
                            index: 0,
                          },
                        },
                        value: 123,
                      },
                    ],
                  },
                },
                {},
              ],
            }),
          );
          return "";
        },
      });

      const config = createTestConfig({
        languages: [language],
        tempDir: tmpDir,
        dbLocation: path.resolve(tmpDir, "codeql_databases"),
      });
      fs.mkdirSync(util.getCodeQLDatabasePath(config, language), {
        recursive: true,
      });

      const statusReport = await runQueries(
        tmpDir,
        memoryFlag,
        threadsFlag,
        undefined,
        undefined,
        codeql,
        config,
        getRunnerLogger(true),
        createFeatures([Feature.QaTelemetryEnabled]),
      );
      t.deepEqual(Object.keys(statusReport).sort(), [
        "analysis_builds_overlay_base_database",
        "analysis_is_diff_informed",
        "analysis_is_overlay",
        `analyze_builtin_queries_${language}_duration_ms`,
        "event_reports",
        `interpret_results_${language}_duration_ms`,
      ]);
      for (const eventReport of statusReport.event_reports!) {
        t.deepEqual(eventReport.event, "codeql database interpret-results");
        t.true("properties" in eventReport);
        t.true("alertCounts" in eventReport.properties!);
      }
    }
  });
});

test("resolveQuerySuiteAlias", (t) => {
  // default query suite names should resolve to something language-specific ending in `.qls`.
  for (const suite of defaultSuites) {
    const resolved = resolveQuerySuiteAlias(KnownLanguage.go, suite);
    t.assert(
      path.extname(resolved) === ".qls",
      "Resolved default suite doesn't end in .qls",
    );
    t.assert(
      resolved.indexOf(KnownLanguage.go) >= 0,
      "Resolved default suite doesn't contain language name",
    );
  }

  // other inputs should be returned unchanged
  const names = ["foo", "bar", "codeql/go-queries@1.0"];

  for (const name of names) {
    t.deepEqual(resolveQuerySuiteAlias(KnownLanguage.go, name), name);
  }
});

test("addSarifExtension", (t) => {
  for (const language of Object.values(KnownLanguage)) {
    t.deepEqual(addSarifExtension(CodeScanning, language), `${language}.sarif`);
    t.deepEqual(
      addSarifExtension(CodeQuality, language),
      `${language}.quality.sarif`,
    );
    t.is(addSarifExtension(RiskAssessment, language), `${language}.csra.sarif`);
  }
});

test("diffRangeExtensionPackContents", (t) => {
  const output = diffRangeExtensionPackContents(
    [
      {
        path: "main.js",
        startLine: 10,
        endLine: 20,
      },
    ],
    "/checkout/path",
  );

  const expected = fs.readFileSync(
    `${__dirname}/../src/testdata/pr-diff-range.yml`,
    "utf8",
  );
  t.deepEqual(output, expected);
});
