import * as core from "@actions/core";
import { v4 as uuidV4 } from "uuid";

import {
  getActionVersion,
  getOptionalInput,
  getRequiredInput,
  getTemporaryDirectory,
} from "./actions-util";
import { AnalysisKind, getAnalysisKinds } from "./analyses";
import { getGitHubVersion } from "./api-client";
import { CodeQL } from "./codeql";
import { getRawLanguagesNoAutodetect } from "./config-utils";
import { EnvVar } from "./environment";
import { initFeatures } from "./feature-flags";
import { initCodeQL } from "./init";
import { getActionsLogger, Logger } from "./logging";
import { getRepositoryNwo } from "./repository";
import { ToolsSource } from "./setup-codeql";
import {
  ActionName,
  InitStatusReport,
  InitToolsDownloadFields,
  createStatusReportBase,
  getActionsStatus,
  sendStatusReport,
  sendUnhandledErrorStatusReport,
} from "./status-report";
import { ToolsDownloadStatusReport } from "./tools-download";
import {
  checkDiskUsage,
  checkForTimeout,
  checkGitHubVersionInRange,
  getRequiredEnvParam,
  initializeEnvironment,
  ConfigurationError,
  wrapError,
  checkActionVersion,
  getErrorMessage,
} from "./util";

/**
 * Helper function to send a full status report for this action.
 */
async function sendCompletedStatusReport(
  startedAt: Date,
  toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined,
  toolsFeatureFlagsValid: boolean | undefined,
  toolsSource: ToolsSource,
  toolsVersion: string,
  logger: Logger,
  error?: Error,
): Promise<void> {
  const statusReportBase = await createStatusReportBase(
    ActionName.SetupCodeQL,
    getActionsStatus(error),
    startedAt,
    undefined,
    await checkDiskUsage(logger),
    logger,
    error?.message,
    error?.stack,
  );

  if (statusReportBase === undefined) {
    return;
  }

  const initStatusReport: InitStatusReport = {
    ...statusReportBase,
    tools_input: getOptionalInput("tools") || "",
    tools_resolved_version: toolsVersion,
    tools_source: toolsSource || ToolsSource.Unknown,
    workflow_languages: "",
  };

  const initToolsDownloadFields: InitToolsDownloadFields = {};

  if (toolsDownloadStatusReport?.downloadDurationMs !== undefined) {
    initToolsDownloadFields.tools_download_duration_ms =
      toolsDownloadStatusReport.downloadDurationMs;
  }
  if (toolsFeatureFlagsValid !== undefined) {
    initToolsDownloadFields.tools_feature_flags_valid = toolsFeatureFlagsValid;
  }

  await sendStatusReport({ ...initStatusReport, ...initToolsDownloadFields });
}

/** The main behaviour of this action. */
async function run(startedAt: Date): Promise<void> {
  // To capture errors appropriately, keep as much code within the try-catch as
  // possible, and only use safe functions outside.

  const logger = getActionsLogger();

  let codeql: CodeQL;
  let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
  let toolsFeatureFlagsValid: boolean | undefined;
  let toolsSource: ToolsSource;
  let toolsVersion: string;

  try {
    initializeEnvironment(getActionVersion());

    const apiDetails = {
      auth: getRequiredInput("token"),
      externalRepoAuth: getOptionalInput("external-repository-token"),
      url: getRequiredEnvParam("GITHUB_SERVER_URL"),
      apiURL: getRequiredEnvParam("GITHUB_API_URL"),
    };

    const gitHubVersion = await getGitHubVersion();
    checkGitHubVersionInRange(gitHubVersion, logger);
    checkActionVersion(getActionVersion(), gitHubVersion);

    const repositoryNwo = getRepositoryNwo();

    const features = initFeatures(
      gitHubVersion,
      repositoryNwo,
      getTemporaryDirectory(),
      logger,
    );

    const jobRunUuid = uuidV4();
    logger.info(`Job run UUID is ${jobRunUuid}.`);
    core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid);

    const statusReportBase = await createStatusReportBase(
      ActionName.SetupCodeQL,
      "starting",
      startedAt,
      undefined,
      await checkDiskUsage(logger),
      logger,
    );
    if (statusReportBase !== undefined) {
      await sendStatusReport(statusReportBase);
    }
    const codeQLDefaultVersionInfo =
      await features.getEnabledDefaultCliVersions(gitHubVersion.type);
    toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid;
    const rawLanguages = getRawLanguagesNoAutodetect(
      getOptionalInput("languages"),
    );
    const analysisKinds = await getAnalysisKinds(logger, features);
    const initCodeQLResult = await initCodeQL(
      getOptionalInput("tools"),
      apiDetails,
      getTemporaryDirectory(),
      gitHubVersion.type,
      codeQLDefaultVersionInfo,
      rawLanguages,
      analysisKinds.length === 1 &&
        analysisKinds[0] === AnalysisKind.CodeScanning,
      features,
      logger,
    );
    codeql = initCodeQLResult.codeql;
    toolsDownloadStatusReport = initCodeQLResult.toolsDownloadStatusReport;
    toolsVersion = initCodeQLResult.toolsVersion;
    toolsSource = initCodeQLResult.toolsSource;

    core.setOutput("codeql-path", codeql.getPath());
    core.setOutput("codeql-version", (await codeql.getVersion()).version);

    core.exportVariable(EnvVar.SETUP_CODEQL_ACTION_HAS_RUN, "true");
  } catch (unwrappedError) {
    const error = wrapError(unwrappedError);
    core.setFailed(error.message);
    const statusReportBase = await createStatusReportBase(
      ActionName.SetupCodeQL,
      error instanceof ConfigurationError ? "user-error" : "failure",
      startedAt,
      undefined,
      await checkDiskUsage(logger),
      logger,
      error.message,
      error.stack,
    );
    if (statusReportBase !== undefined) {
      await sendStatusReport(statusReportBase);
    }
    return;
  }

  await sendCompletedStatusReport(
    startedAt,
    toolsDownloadStatusReport,
    toolsFeatureFlagsValid,
    toolsSource,
    toolsVersion,
    logger,
  );
}

/** Run the action and catch any unhandled errors. */
export async function runWrapper(): Promise<void> {
  const startedAt = new Date();
  const logger = getActionsLogger();
  try {
    await run(startedAt);
  } catch (error) {
    core.setFailed(`setup-codeql action failed: ${getErrorMessage(error)}`);
    await sendUnhandledErrorStatusReport(
      ActionName.SetupCodeQL,
      startedAt,
      error,
      logger,
    );
  }
  await checkForTimeout();
}
