import { parseArgs } from "node:util";
import { runSecretLint, SecretLintOptions } from "./index.js";
import { runConfigCreator } from "./create-secretlintrc.js";
import { secretLintProfiler } from "@secretlint/profiler";
import { getFormatterList } from "@secretlint/formatter";
import { getPackageJson } from "@secretlint/resolver";
import debug0 from "debug";
import { text } from "node:stream/consumers";

const debug = debug0("secretlint");

const helpMessage = `
Usage
  $ secretlint [file|glob*]

Note
  supported glob syntax is based on microglob
  https://github.com/micromatch/micromatch#matching-features

Options
  --init             setup config file. Create .secretlintrc.json file from your package.json
  --format           [String] formatter name. Default: "stylish". Available Formatter: ${getFormatterList()
      .map((item) => item.name)
      .join(", ")}
  --output           [path:String] output file path that is written of reported result.
  --no-color         disable ANSI-color of output.
  --no-terminalLink  disable terminalLink of output.
  --maskSecrets      enable masking of secret values. replace actual secrets with "***".
  --secretlintrc     [path:String] path to .secretlintrc config file. Default: .secretlintrc.*
  --secretlintignore [path:String] path to .secretlintignore file. Default: .secretlintignore
  --stdinFileName    [String] filename to process STDIN content. Some rules depend on filename to check content.

Options for Developer
  --profile          Enable performance profile. 
  --secretlintrcJSON [String] a JSON string of .secretlintrc. use JSON string instead of rc file.

Experimental Options
  --locale            [String] locale tag for translating message. Default: en

Examples
  $ secretlint ./README.md
  # glob pattern should be wrapped with double quote
  $ secretlint "**/*"
  $ secretlint "source/**/*.ini"
  # found secrets and mask the secrets
  $ secretlint .zsh_history --format=mask-result --output=.zsh_history
  # lint STDIN content instead of file
  $ echo "SECRET CONTENT" | secretlint --stdinFileName=secret.txt
  
Exit Status
  Secretlint exits with the following values:

  - 0: 
    - Linting succeeded, no errors found. 
    - Found lint error but --output is specified.
  - 1: 
    - Linting failed, errors found.
  - 2: 
    - Unexpected error occurred, fatal error.
`;
const OPTION_TYPE_STRING = "string" as const;
const OPTION_TYPE_BOOLEAN = "boolean" as const;
const options = {
    init: {
        type: OPTION_TYPE_BOOLEAN,
    },
    format: {
        type: OPTION_TYPE_STRING,
        default: "stylish",
    },
    output: {
        type: OPTION_TYPE_STRING,
    },
    "no-color": {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
    "no-terminalLink": {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
    maskSecrets: {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
    secretlintrc: {
        type: OPTION_TYPE_STRING,
    },
    secretlintignore: {
        type: OPTION_TYPE_STRING,
        default: ".secretlintignore",
    },
    stdinFileName: {
        type: OPTION_TYPE_STRING,
    },
    profile: {
        type: OPTION_TYPE_BOOLEAN,
    },
    secretlintrcJSON: {
        type: OPTION_TYPE_STRING,
    },
    locale: {
        type: OPTION_TYPE_STRING,
    },
    /**
     * CLI enable ANSI-color of output by default
     */
    color: {
        type: OPTION_TYPE_BOOLEAN,
        default: true,
    },
    /**
     * CLI enable terminalLink by default.
     * Some formatter will output that includes clickable clink
     * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
     */
    terminalLink: {
        type: OPTION_TYPE_BOOLEAN,
        default: true,
    },
    // DEBUG option
    cwd: {
        type: OPTION_TYPE_STRING,
        default: process.cwd(),
    },
    debug: {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
    help: { type: OPTION_TYPE_BOOLEAN, default: false },
    version: { type: OPTION_TYPE_BOOLEAN, default: false },
    "no-config": {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
    "no-package": {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
    $schema: {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
    extension: {
        type: OPTION_TYPE_STRING,
    },
    reporter: {
        type: OPTION_TYPE_STRING,
    },
    slow: {
        type: OPTION_TYPE_STRING,
    },
    timeout: {
        type: OPTION_TYPE_STRING,
    },
    ui: {
        type: OPTION_TYPE_STRING,
    },
    "watch-ignore": {
        type: OPTION_TYPE_STRING,
    },
    diff: {
        type: OPTION_TYPE_BOOLEAN,
        default: false,
    },
};
const { values, positionals } = parseArgs({ options, allowPositionals: true });
export const cli = {
    input: positionals,
    flags: values,
};

const getStdin = async () => {
    return await text(process.stdin);
};
const readCliOptions = async ({ input = cli.input, flags = cli.flags }): Promise<SecretLintOptions> => {
    // if specify stdinFileName, read from stdin
    if (flags.stdinFileName) {
        const stdinContent = await getStdin();
        return {
            stdinContent,
            stdinFileName: flags.stdinFileName,
            outputFilePath: flags.output,
            ignoreFilePath: flags.secretlintignore,
            cwd: flags.cwd,
        };
    } else {
        return {
            filePathOrGlobList: input,
            outputFilePath: flags.output,
            ignoreFilePath: flags.secretlintignore,
            cwd: flags.cwd,
        };
    }
};
export const run = async (
    input = cli.input,
    flags = cli.flags
): Promise<{ exitStatus: number; stdout: string | null; stderr: Error | null }> => {
    if (flags.help) {
        return {
            exitStatus: 0,
            stdout: helpMessage,
            stderr: null,
        };
    }
    if (flags.version) {
        const packageJson = getPackageJson(import.meta.url);
        return {
            exitStatus: 0,
            stdout: packageJson?.version ?? "",
            stderr: null,
        };
    }
    secretLintProfiler.mark({
        type: "secretlint>cli::start",
    });
    if (flags.debug) {
        const debug = await import("debug");
        debug.default?.enable?.("*secretlint*");
    }
    const cwd = flags.cwd;
    debug("input: %O", input);
    debug("flags: %O", flags);
    if (flags.init) {
        return runConfigCreator({
            cwd,
        });
    }
    const cliOptions = await readCliOptions({
        input,
        flags,
    });
    debug("cliOptions %O", cliOptions);
    return runSecretLint({
        cliOptions,
        engineOptions: flags.secretlintrcJSON
            ? {
                  // Parse config string as JSON
                  configFileJSON: JSON.parse(flags.secretlintrcJSON),
                  cwd: cwd,
                  formatter: flags.format,
                  color: flags.color,
                  terminalLink: flags.terminalLink,
                  locale: flags.locale,
                  maskSecrets: flags.maskSecrets,
              }
            : {
                  configFilePath: flags.secretlintrc,
                  cwd: cwd,
                  formatter: flags.format,
                  color: flags.color,
                  terminalLink: flags.terminalLink,
                  locale: flags.locale,
                  maskSecrets: flags.maskSecrets,
              },
    }).finally(async () => {
        secretLintProfiler.mark({
            type: "secretlint>cli::end",
        });
        if (flags.profile) {
            const measures = await secretLintProfiler.getMeasures();
            const cwd = flags.cwd;
            if (flags.format === "json") {
                console.log(JSON.stringify(measures, null, 4));
            } else {
                measures.forEach((entry) => {
                    const takenMs = entry.duration;
                    const takenRoundMs = Math.round((takenMs + Number.EPSILON) * 100) / 100;
                    console.log(`${entry.name.replace(cwd, "<cwd>")} - ${takenRoundMs}ms`);
                });
            }
        }
    });
};
