const tl = require("azure-pipelines-task-lib/task");
const path = require("path");
const fs = require("fs");
const { execSync, execFileSync } = require("child_process");

const { runCodeAnalyzer } = require("./lib/codeAnalyzerRunner");
const { callSecurityScanApi } = require("./lib/securityScanApi");

function runShellCommand(command, title) {
  if (!command || !command.trim()) {
    console.log(`${title}: skipped because command is empty.`);
    return;
  }

  console.log(`========== ${title} ==========`);
  console.log(command);
  console.log("================================");

  execSync(command, {
    stdio: "inherit",
    shell: true,
    env: process.env
  });
}

function runScript(scriptPath, args, title) {
  if (!fs.existsSync(scriptPath)) {
    throw new Error(`${title}: script not found at ${scriptPath}`);
  }

  console.log(`========== ${title} ==========`);
  console.log(`Script: ${scriptPath}`);
  console.log(`Args  : ${args.join(" ")}`);
  console.log("================================");

  execFileSync("bash", [scriptPath, ...args], {
    stdio: "inherit",
    env: process.env
  });
}

function validateKubernetesInputs(runMode, clusterName, namespace, stageName) {
  if (runMode === "kubernetes") {
    if (!clusterName || !clusterName.trim()) {
      throw new Error(`${stageName}: Kubernetes Cluster Name is required when Run Mode is kubernetes.`);
    }

    if (!namespace || !namespace.trim()) {
      throw new Error(`${stageName}: Kubernetes Namespace is required when Run Mode is kubernetes.`);
    }
  }
}

function runTrivyStandalone(tl, imageName) {
  const trivyMode = (tl.getInput("trivyMode", false) || "image").toLowerCase();

  let trivyTarget = tl.getInput("trivyTarget", false) || "";

  /*
    If user does not provide Trivy Target,
    use Docker Image Name as default target.
  */
  if (!trivyTarget.trim() && imageName && imageName.trim()) {
    trivyTarget = imageName.trim();
  }

  if (!trivyTarget.trim()) {
    throw new Error(
      "Trivy Target is required when Container Scan Run Mode is standalone. Example: alpine:3.20 or eshoponweb:$(Build.BuildNumber)"
    );
  }

  const allowedModes = ["image", "container", "fs", "repo"];

  if (!allowedModes.includes(trivyMode)) {
    throw new Error(
      `Invalid Trivy Mode '${trivyMode}'. Allowed values: ${allowedModes.join(", ")}`
    );
  }

  const scriptPath = path.join(__dirname, "trivy-standalone-wrapper.sh");

  console.log("========== Trivy Standalone Scan ==========");
  console.log("Type   : containerization");
  console.log("Engine : trivy");
  console.log(`Mode   : ${trivyMode}`);
  console.log(`Target : ${trivyTarget}`);
  console.log("==========================================");

  runScript(
    scriptPath,
    ["--mode", trivyMode, "--target", trivyTarget],
    "Run Trivy Standalone Wrapper"
  );
}

function runZapStandalone(tl, targetUrl) {
  const zapMode = (tl.getInput("zapMode", false) || "baseline").toLowerCase();

  /*
    Prefer zapTarget.
    If not provided, fallback to Dynamic Scan Target URL.
  */
  let zapTarget = tl.getInput("zapTarget", false) || "";
  if (!zapTarget.trim() && targetUrl && targetUrl.trim()) {
    zapTarget = targetUrl.trim();
  }

  const zapApiFormat = (tl.getInput("zapApiFormat", false) || "openapi").toLowerCase();

  const zapReportDir =
    tl.getInput("zapReportDir", false) ||
    `${process.env.SYSTEM_DEFAULTWORKINGDIRECTORY || process.cwd()}/zap-reports`;

  if (!zapTarget.trim()) {
    throw new Error(
      "ZAP Target URL is required when Dynamic Scan Run Mode is standalone. Example: http://127.0.0.1:3001"
    );
  }

  const allowedZapModes = ["baseline", "full", "api"];

  if (!allowedZapModes.includes(zapMode)) {
    throw new Error(
      `Invalid ZAP Mode '${zapMode}'. Allowed values: ${allowedZapModes.join(", ")}`
    );
  }

  const allowedApiFormats = ["openapi", "graphql", "soap"];

  if (zapMode === "api" && !allowedApiFormats.includes(zapApiFormat)) {
    throw new Error(
      `Invalid ZAP API Format '${zapApiFormat}'. Allowed values: ${allowedApiFormats.join(", ")}`
    );
  }

  const scriptPath = path.join(__dirname, "zap-standalone-wrapper.sh");

  const args = ["--mode", zapMode, "--target", zapTarget];

  if (zapMode === "api") {
    args.push("--api-format", zapApiFormat);
    args.push("--report-dir", zapReportDir);
  }

  console.log("========== ZAP Standalone Scan ==========");
  console.log("Type   : dynamic");
  console.log("Engine : zap");
  console.log(`Mode   : ${zapMode}`);
  console.log(`Target : ${zapTarget}`);

  if (zapMode === "api") {
    console.log(`API Format : ${zapApiFormat}`);
    console.log(`Report Dir : ${zapReportDir}`);
  }

  console.log("========================================");

  runScript(scriptPath, args, "Run ZAP Standalone Wrapper");
}

async function run() {
  try {
    const serviceConnection = tl.getInput("cavScanServiceConnection", true);
    const allowInsecureSSL = tl.getBoolInput("allowInsecureSSL", false);

    console.log("========== EXTENSION CODE VERSION: standalone-shell-only-v3 ==========");
    /*
      Service connection is used for static Code Analyzer / SonarQube token generation.
      Example:
      https://demo-cicd.cav-test.com:4444
    */
    const codeAnalyzerBaseUrl = tl
      .getEndpointUrl(serviceConnection, true)
      .replace(/\/$/, "");

    const auth = tl.getEndpointAuthorization(serviceConnection, false);

    let apiToken = "";

    if (auth && auth.parameters && auth.parameters.apitoken) {
      apiToken = auth.parameters.apitoken.trim();
      tl.setSecret(apiToken);
    }

    /*
      Separate REST API base URL for Trivy/ZAP update call.
      Example:
      https://sonar1.cav-test.com:4444
    */
    const securityScanApiBaseUrl = tl
      .getInput("securityScanApiBaseUrl", true)
      .replace(/\/$/, "");

    const applicationType = (tl.getInput("applicationType", true) || "dotnet").toLowerCase();

    const runStaticScan = tl.getBoolInput("runStaticScan", false);
    const staticEngine = (tl.getInput("staticEngine", false) || "sonarqube").toLowerCase();

    const buildCommand = tl.getInput("buildCommand", true);
    const dockerBuildCommand = tl.getInput("dockerBuildCommand", true);

    const containerEngine = (tl.getInput("containerEngine", true) || "trivy").toLowerCase();
    const containerRunMode = (tl.getInput("containerRunMode", true) || "standalone").toLowerCase();
    const imageName = tl.getInput("imageName", false) || "";

    const containerClusterName = tl.getInput("containerClusterName", false) || "";
    const containerNamespace = tl.getInput("containerNamespace", false) || "default";

    const deployCommand = tl.getInput("deployCommand", false) || "";

    const dynamicEngine = (tl.getInput("dynamicEngine", true) || "zap").toLowerCase();
    const dynamicRunMode = (tl.getInput("dynamicRunMode", true) || "standalone").toLowerCase();
    const targetUrl = tl.getInput("targetUrl", false) || "";

    const dynamicClusterName = tl.getInput("dynamicClusterName", false) || "";
    const dynamicNamespace = tl.getInput("dynamicNamespace", false) || "default";

    console.log("========== Cavisson Full Security Pipeline ==========");
    console.log(`Code Analyzer Base URL : ${codeAnalyzerBaseUrl}`);
    console.log(`Security REST Base URL : ${securityScanApiBaseUrl}`);
    console.log(`Application Type       : ${applicationType}`);
    console.log(`Static Type            : static`);
    console.log(`Static Engine          : ${staticEngine}`);
    console.log(`Container Type         : containerization`);
    console.log(`Container Engine       : ${containerEngine}`);
    console.log(`Container Run Mode     : ${containerRunMode}`);
    console.log(`Dynamic Type           : dynamic`);
    console.log(`Dynamic Engine         : ${dynamicEngine}`);
    console.log(`Dynamic Run Mode       : ${dynamicRunMode}`);
    console.log("====================================================");

    /*
      1. Static scan
      type = static
      engine = sonarqube
    */
    if (runStaticScan) {
      console.log("========== Static Scan ==========");
      console.log("Type   : static");
      console.log(`Engine : ${staticEngine}`);
      console.log("=================================");

      await runCodeAnalyzer(tl, {
        staticEngine,
        baseUrl: codeAnalyzerBaseUrl,
        apiToken
      });
    } else {
      console.log("Static scan skipped.");
    }

    /*
      2. Build application
    */
    runShellCommand(buildCommand, "Build Application");

    /*
      3. Docker image build
    */
    runShellCommand(dockerBuildCommand, "Docker Image Build");

    /*
      4. Containerization scan
      type = containerization
      engine = trivy
    */
    validateKubernetesInputs(
      containerRunMode,
      containerClusterName,
      containerNamespace,
      "Container Scan"
    );

    if (containerEngine !== "trivy") {
      throw new Error(
        `Container engine '${containerEngine}' is available in dropdown, but currently only Trivy is implemented.`
      );
    }

    console.log("========== Containerization Scan ==========");
    console.log("Type       : containerization");
    console.log(`Engine     : ${containerEngine}`);
    console.log(`Run Mode   : ${containerRunMode}`);
    console.log(`Image Name : ${imageName || "not provided"}`);

    if (containerRunMode === "kubernetes") {
      console.log(`Cluster    : ${containerClusterName}`);
      console.log(`Namespace  : ${containerNamespace}`);
      console.log("Kubernetes Trivy execution is not implemented yet. Only metadata is validated/logged.");
    }

    console.log("==========================================");

    /*
      Run Trivy standalone shell:
      ./trivy-standalone-wrapper.sh --mode image --target alpine:3.20
    */
    if (containerEngine === "trivy" && containerRunMode === "standalone") {
  console.log("Container Run Mode is standalone. Running Trivy shell scan only.");
  runTrivyStandalone(tl, imageName);
} else if (containerEngine === "trivy" && containerRunMode === "kubernetes") {
  console.log("Container Run Mode is kubernetes. Calling Trivy REST API only.");

  await callSecurityScanApi({
    securityScanApiBaseUrl,
    apiToken,
    allowInsecureSSL,
    stageName: "Trivy Containerization Scan",
    payload: {
      scanDast: false,
      scanSca: true
    }
  });
} else {
  throw new Error(`Unsupported container run mode: ${containerRunMode}`);
}

    /*
      5. Deploy / run application
    */
    runShellCommand(deployCommand, "Deploy / Run Application");

    /*
      6. Dynamic scan
      type = dynamic
      engine = zap
    */
    validateKubernetesInputs(
      dynamicRunMode,
      dynamicClusterName,
      dynamicNamespace,
      "Dynamic Scan"
    );

    if (dynamicEngine !== "zap") {
      throw new Error(
        `Dynamic engine '${dynamicEngine}' is available in dropdown, but currently only ZAP is implemented.`
      );
    }

    console.log("========== Dynamic Scan ==========");
    console.log("Type       : dynamic");
    console.log(`Engine     : ${dynamicEngine}`);
    console.log(`Run Mode   : ${dynamicRunMode}`);
    console.log(`Target URL : ${targetUrl || "not provided"}`);

    if (dynamicRunMode === "kubernetes") {
      console.log(`Cluster    : ${dynamicClusterName}`);
      console.log(`Namespace  : ${dynamicNamespace}`);
      console.log("Kubernetes ZAP execution is not implemented yet. Only metadata is validated/logged.");
    }

    console.log("==================================");

    /*
      Run ZAP standalone shell:
      baseline/full:
      ./zap-standalone-wrapper.sh --mode full --target http://127.0.0.1:3001

      api:
      ./zap-standalone-wrapper.sh \
        --mode api \
        --target http://127.0.0.1:8080/swagger/v1/swagger.json \
        --api-format openapi \
        --report-dir ~/work/zap-reports
    */
    if (dynamicEngine === "zap" && dynamicRunMode === "standalone") {
  console.log("Dynamic Run Mode is standalone. Running ZAP shell scan only.");
  runZapStandalone(tl, targetUrl);
} else if (dynamicEngine === "zap" && dynamicRunMode === "kubernetes") {
  console.log("Dynamic Run Mode is kubernetes. Calling ZAP REST API only.");

  await callSecurityScanApi({
    securityScanApiBaseUrl,
    apiToken,
    allowInsecureSSL,
    stageName: "ZAP Dynamic Scan",
    payload: {
      scanDast: true,
      scanSca: false
    }
  });
} else {
  throw new Error(`Unsupported dynamic run mode: ${dynamicRunMode}`);
}

    tl.setResult(
      tl.TaskResult.Succeeded,
      "Cavisson full security pipeline completed successfully."
    );
  } catch (error) {
    const message = error && error.message ? error.message : String(error);
    tl.setResult(tl.TaskResult.Failed, message);
  }
}

run();  