/*
 This file is part of GNU Taler
 (C) 2019 Taler Systems S.A.
 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.
 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see 
 */
/**
 * Imports.
 */
import { deepStrictEqual } from "assert";
import fs from "fs";
import os from "os";
import path from "path";
import {
  Amounts,
  clk,
  Configuration,
  decodeCrock,
  Logger,
  rsaBlind,
  setGlobalLogLevelFromString,
} from "@gnu-taler/taler-util";
import { runBench1 } from "./bench1.js";
import { runBench2 } from "./bench2.js";
import { runBench3 } from "./bench3.js";
import { runEnv1 } from "./env1.js";
import { GlobalTestState, runTestWithState } from "./harness/harness.js";
import { getTestInfo, runTests } from "./integrationtests/testrunner.js";
import { lintExchangeDeployment } from "./lint.js";
import { runEnvFull } from "./env-full.js";
const logger = new Logger("taler-harness:index.ts");
process.on("unhandledRejection", (error: any) => {
  logger.error("unhandledRejection", error.message);
  logger.error("stack", error.stack);
  process.exit(2);
});
declare const __VERSION__: string;
function printVersion(): void {
  console.log(__VERSION__);
  process.exit(0);
}
export const testingCli = clk
  .program("testing", {
    help: "Command line interface for the GNU Taler test/deployment harness.",
  })
  .maybeOption("log", ["-L", "--log"], clk.STRING, {
    help: "configure log level (NONE, ..., TRACE)",
    onPresentHandler: (x) => {
      setGlobalLogLevelFromString(x);
    },
  })
  .flag("version", ["-v", "--version"], {
    onPresentHandler: printVersion,
  })
  .flag("verbose", ["-V", "--verbose"], {
    help: "Enable verbose output.",
  });
const advancedCli = testingCli.subcommand("advancedArgs", "advanced", {
  help: "Subcommands for advanced operations (only use if you know what you're doing!).",
});
advancedCli
  .subcommand("bench1", "bench1", {
    help: "Run the 'bench1' benchmark",
  })
  .requiredOption("configJson", ["--config-json"], clk.STRING)
  .action(async (args) => {
    let config: any;
    try {
      config = JSON.parse(args.bench1.configJson);
    } catch (e) {
      console.log("Could not parse config JSON");
    }
    await runBench1(config);
  });
advancedCli
  .subcommand("bench2", "bench2", {
    help: "Run the 'bench2' benchmark",
  })
  .requiredOption("configJson", ["--config-json"], clk.STRING)
  .action(async (args) => {
    let config: any;
    try {
      config = JSON.parse(args.bench2.configJson);
    } catch (e) {
      console.log("Could not parse config JSON");
    }
    await runBench2(config);
  });
advancedCli
  .subcommand("bench3", "bench3", {
    help: "Run the 'bench3' benchmark",
  })
  .requiredOption("configJson", ["--config-json"], clk.STRING)
  .action(async (args) => {
    let config: any;
    try {
      config = JSON.parse(args.bench3.configJson);
    } catch (e) {
      console.log("Could not parse config JSON");
    }
    await runBench3(config);
  });
advancedCli
  .subcommand("envFull", "env-full", {
    help: "Run a test environment for bench1",
  })
  .action(async (args) => {
    const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "taler-env-full-"));
    const testState = new GlobalTestState({
      testDir,
    });
    await runTestWithState(testState, runEnvFull, "env-full", true);
  });
advancedCli
  .subcommand("env1", "env1", {
    help: "Run a test environment for bench1",
  })
  .action(async (args) => {
    const testDir = fs.mkdtempSync(path.join(os.tmpdir(), "taler-env1-"));
    const testState = new GlobalTestState({
      testDir,
    });
    await runTestWithState(testState, runEnv1, "env1", true);
  });
const deploymentCli = testingCli.subcommand("deploymentArgs", "deployment", {
  help: "Subcommands for handling GNU Taler deployments.",
});
deploymentCli
  .subcommand("lintExchange", "lint-exchange", {
    help: "Run checks on the exchange deployment.",
  })
  .flag("cont", ["--continue"], {
    help: "Continue after errors if possible",
  })
  .flag("debug", ["--debug"], {
    help: "Output extra debug info",
  })
  .action(async (args) => {
    await lintExchangeDeployment(
      args.lintExchange.debug,
      args.lintExchange.cont,
    );
  });
deploymentCli
  .subcommand("coincfg", "gen-coin-config", {
    help: "Generate a coin/denomination configuration for the exchange.",
  })
  .requiredOption("minAmount", ["--min-amount"], clk.STRING, {
    help: "Smallest denomination",
  })
  .requiredOption("maxAmount", ["--max-amount"], clk.STRING, {
    help: "Largest denomination",
  })
  .action(async (args) => {
    let out = "";
    const stamp = Math.floor(new Date().getTime() / 1000);
    const min = Amounts.parseOrThrow(args.coincfg.minAmount);
    const max = Amounts.parseOrThrow(args.coincfg.maxAmount);
    if (min.currency != max.currency) {
      console.error("currency mismatch");
      process.exit(1);
    }
    const currency = min.currency;
    let x = min;
    let n = 1;
    out += "# Coin configuration for the exchange.\n";
    out += '# Should be placed in "/etc/taler/conf.d/exchange-coins.conf".\n';
    out += "\n";
    while (Amounts.cmp(x, max) < 0) {
      out += `[COIN-${currency}-n${n}-t${stamp}]\n`;
      out += `VALUE = ${Amounts.stringify(x)}\n`;
      out += `DURATION_WITHDRAW = 7 days\n`;
      out += `DURATION_SPEND = 2 years\n`;
      out += `DURATION_LEGAL = 6 years\n`;
      out += `FEE_WITHDRAW = ${currency}:0\n`;
      out += `FEE_DEPOSIT = ${Amounts.stringify(min)}\n`;
      out += `FEE_REFRESH = ${currency}:0\n`;
      out += `FEE_REFUND = ${currency}:0\n`;
      out += `RSA_KEYSIZE = 2048\n`;
      out += "\n";
      x = Amounts.add(x, x).amount;
      n++;
    }
    console.log(out);
  });
const deploymentConfigCli = deploymentCli.subcommand("configArgs", "config", {
  help: "Subcommands the Taler configuration.",
});
deploymentConfigCli
  .subcommand("show", "show")
  .flag("diagnostics", ["-d", "--diagnostics"])
  .maybeArgument("cfgfile", clk.STRING, {})
  .action(async (args) => {
    const cfg = Configuration.load(args.show.cfgfile);
    console.log(
      cfg.stringify({
        diagnostics: args.show.diagnostics,
      }),
    );
  });
testingCli.subcommand("logtest", "logtest").action(async (args) => {
  logger.trace("This is a trace message.");
  logger.info("This is an info message.");
  logger.warn("This is an warning message.");
  logger.error("This is an error message.");
});
testingCli
  .subcommand("listIntegrationtests", "list-integrationtests")
  .action(async (args) => {
    for (const t of getTestInfo()) {
      let s = t.name;
      if (t.suites.length > 0) {
        s += ` (suites: ${t.suites.join(",")})`;
      }
      if (t.excludeByDefault) {
        s += ` [excluded by default]`;
      }
      if (t.experimental) {
        s += ` [experimental]`;
      }
      console.log(s);
    }
  });
testingCli
  .subcommand("runIntegrationtests", "run-integrationtests")
  .maybeArgument("pattern", clk.STRING, {
    help: "Glob pattern to select which tests to run",
  })
  .maybeOption("suites", ["--suites"], clk.STRING, {
    help: "Only run selected suites (comma-separated list)",
  })
  .flag("dryRun", ["--dry"], {
    help: "Only print tests that will be selected to run.",
  })
  .flag("experimental", ["--experimental"], {
    help: "Include tests marked as experimental",
  })
  .flag("quiet", ["--quiet"], {
    help: "Produce less output.",
  })
  .action(async (args) => {
    await runTests({
      includePattern: args.runIntegrationtests.pattern,
      suiteSpec: args.runIntegrationtests.suites,
      dryRun: args.runIntegrationtests.dryRun,
      verbosity: args.runIntegrationtests.quiet ? 0 : 1,
      includeExperimental: args.runIntegrationtests.experimental ?? false,
    });
  });
async function read(stream: NodeJS.ReadStream) {
  const chunks = [];
  for await (const chunk of stream) chunks.push(chunk);
  return Buffer.concat(chunks).toString("utf8");
}
testingCli.subcommand("tvgcheck", "tvgcheck").action(async (args) => {
  const data = await read(process.stdin);
  const lines = data.match(/[^\r\n]+/g);
  if (!lines) {
    throw Error("can't split lines");
  }
  const vals: Record = {};
  let inBlindSigningSection = false;
  for (const line of lines) {
    if (line === "blind signing:") {
      inBlindSigningSection = true;
      continue;
    }
    if (line[0] !== " ") {
      inBlindSigningSection = false;
      continue;
    }
    if (inBlindSigningSection) {
      const m = line.match(/  (\w+) (\w+)/);
      if (!m) {
        console.log("bad format");
        process.exit(2);
      }
      vals[m[1]] = m[2];
    }
  }
  console.log(vals);
  const req = (k: string) => {
    if (!vals[k]) {
      throw Error(`no value for ${k}`);
    }
    return decodeCrock(vals[k]);
  };
  const myBm = rsaBlind(
    req("message_hash"),
    req("blinding_key_secret"),
    req("rsa_public_key"),
  );
  deepStrictEqual(req("blinded_message"), myBm);
  console.log("check passed!");
});
export function main() {
  testingCli.run();
}