/**
 * (c) Copyright IBM Corporation 2015.
 * (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
 * This is licensed under the following license.
 * The Eclipse Public 1.0 License (http://www.eclipse.org/legal/epl-v10.html)
 * U.S. Government Users Restricted Rights:  Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 */

import com.urbancode.air.plugin.AppScanSaaS.NewAirPluginTool
import com.urbancode.air.plugin.AppScanSaaS.SCXRestClient

import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream

private static String shellQuote(String value) {
	return "'" + value.replace("'", "'\\''") + "'"
}

private static Map runCommand(List command) {
	List<String> normalizedCommand = command.collect { arg ->
		arg == null ? "" : String.valueOf(arg)
	}
	ProcessBuilder processBuilder = new ProcessBuilder(normalizedCommand)
	processBuilder.redirectErrorStream(true)
	Process process = processBuilder.start()
	String output = process.inputStream.getText("UTF-8")
	int exitCode = process.waitFor()
	return [exitCode: exitCode, output: output]
}

private static File extractSecagentWar(File zipFile, File extractDirectory) {
	if (extractDirectory.exists()) {
		extractDirectory.deleteDir()
	}
	extractDirectory.mkdirs()

	byte[] buffer = new byte[8192]
	File secagentFile = null

	ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFile))
	try {
		ZipEntry entry = zipInputStream.getNextEntry()
		while (entry != null) {
			File outputFile = new File(extractDirectory, entry.getName())
			if (entry.isDirectory()) {
				outputFile.mkdirs()
			}
			else {
				outputFile.getParentFile()?.mkdirs()
				outputFile.withOutputStream { out ->
					int len
					while ((len = zipInputStream.read(buffer)) > 0) {
						out.write(buffer, 0, len)
					}
				}

				if (outputFile.getName().equalsIgnoreCase("Secagent.war")) {
					secagentFile = outputFile
				}
			}
			zipInputStream.closeEntry()
			entry = zipInputStream.getNextEntry()
		}
	}
	finally {
		zipInputStream.close()
	}

	if (secagentFile == null || !secagentFile.exists()) {
		throw new RuntimeException("Secagent.war was not found in downloaded IAST agent package '${zipFile.absolutePath}'.")
	}

	return secagentFile
}

private static File deploySecagentWarToHost(File sourceWar, String destination) {
	File destinationPath = new File(destination)
	File targetFile

	if (destination.toLowerCase().endsWith(".war")) {
		destinationPath.getParentFile()?.mkdirs()
		targetFile = destinationPath
	}
	else {
		destinationPath.mkdirs()
		targetFile = new File(destinationPath, "Secagent.war")
	}

	if (targetFile.exists()) {
		println "[OK] Secagent.war already exists at ${targetFile.absolutePath}. Skipping copy."
		return targetFile
	}

	Files.copy(sourceWar.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
	return targetFile
}

private static String deploySecagentWarToDocker(File sourceWar, String containerName, String containerDestination) {
	String normalizedContainerDestination = containerDestination.endsWith("/")
		? containerDestination.substring(0, containerDestination.length() - 1)
		: containerDestination

	String targetFilePath = normalizedContainerDestination.toLowerCase().endsWith(".war")
		? normalizedContainerDestination
		: normalizedContainerDestination + "/Secagent.war"

	String targetParentDir = targetFilePath.substring(0, targetFilePath.lastIndexOf('/'))

	def existsCheck = runCommand([
		"docker", "exec", containerName, "sh", "-c", "test -f " + shellQuote(targetFilePath)
	])
	if (existsCheck.exitCode == 0) {
		println "[OK] Secagent.war already exists in container '${containerName}' at ${targetFilePath}. Skipping copy."
		return "docker:${containerName}:${targetFilePath}"
	}

	def mkdirResult = runCommand([
		"docker", "exec", containerName, "sh", "-c", "mkdir -p " + shellQuote(targetParentDir)
	])
	if (mkdirResult.exitCode != 0) {
		throw new RuntimeException("Failed to create destination directory in container '${containerName}'. Output: ${mkdirResult.output}")
	}

	def copyResult = runCommand([
		"docker", "cp", sourceWar.absolutePath, "${containerName}:${targetFilePath}"
	])
	if (copyResult.exitCode != 0) {
		throw new RuntimeException("Failed to copy Secagent.war into container '${containerName}'. Output: ${copyResult.output}")
	}

	println "[OK] Secagent.war copied into container '${containerName}' at ${targetFilePath}."
	return "docker:${containerName}:${targetFilePath}"
}

private static String deploySecagentWar(File sourceWar, String destinationUri) {
	if (destinationUri.toLowerCase().startsWith("docker:")) {
		String dockerSpec = destinationUri.substring("docker:".length())
		int separatorIndex = dockerSpec.indexOf(":")
		if (separatorIndex <= 0 || separatorIndex == dockerSpec.length() - 1) {
			throw new RuntimeException("Invalid docker destination format. Expected docker:containerName:/path/to/dir-or-war")
		}

		String containerName = dockerSpec.substring(0, separatorIndex).trim()
		String containerDestination = dockerSpec.substring(separatorIndex + 1).trim()
		println "[Action] Using docker destination strategy. Container='${containerName}', path='${containerDestination}'."
		return deploySecagentWarToDocker(sourceWar, containerName, containerDestination)
	}

	String hostDestination = destinationUri.toLowerCase().startsWith("host:")
		? destinationUri.substring("host:".length()).trim()
		: destinationUri
	println "[Action] Using host destination strategy. Path='${hostDestination}'."

	File deployedFile = deploySecagentWarToHost(sourceWar, hostDestination)
	return deployedFile.absolutePath
}

private static String getHostTargetPath(String destination) {
	File destinationPath = new File(destination)
	if (destination.toLowerCase().endsWith(".war")) {
		return destinationPath.absolutePath
	}
	else {
		return new File(destinationPath, "Secagent.war").absolutePath
	}
}

private static String getDockerTargetPath(String containerDestination) {
	if (containerDestination.toLowerCase().endsWith(".war")) {
		return containerDestination
	}
	else if (containerDestination.endsWith("/")) {
		return containerDestination + "Secagent.war"
	}
	else {
		return containerDestination + "/Secagent.war"
	}
}

private static boolean hostSecagentWarExists(String destination) {
	String targetPath = getHostTargetPath(destination)
	return new File(targetPath).exists()
}

private static boolean dockerSecagentWarExists(String containerName, String containerDestination) {
	String targetFilePath = getDockerTargetPath(containerDestination)
	def existsCheck = runCommand([
		"docker", "exec", containerName, "sh", "-c", "test -f " + shellQuote(targetFilePath)
	])
	return existsCheck.exitCode == 0
}

private static boolean secagentWarExists(String destinationUri) {
	if (destinationUri.toLowerCase().startsWith("docker:")) {
		String dockerSpec = destinationUri.substring("docker:".length())
		int separatorIndex = dockerSpec.indexOf(":")
		if (separatorIndex <= 0 || separatorIndex == dockerSpec.length() - 1) {
			return false
		}
		String containerName = dockerSpec.substring(0, separatorIndex).trim()
		String containerDestination = dockerSpec.substring(separatorIndex + 1).trim()
		return dockerSecagentWarExists(containerName, containerDestination)
	}

	String hostDestination = destinationUri.toLowerCase().startsWith("host:")
		? destinationUri.substring("host:".length()).trim()
		: destinationUri
	return hostSecagentWarExists(hostDestination)
}

private static String getDeployedWarLocation(String destinationUri) {
	if (destinationUri.toLowerCase().startsWith("docker:")) {
		String dockerSpec = destinationUri.substring("docker:".length())
		int separatorIndex = dockerSpec.indexOf(":")
		String containerName = dockerSpec.substring(0, separatorIndex).trim()
		String containerDestination = dockerSpec.substring(separatorIndex + 1).trim()
		String targetFilePath = getDockerTargetPath(containerDestination)
		return "docker:${containerName}:${targetFilePath}"
	}

	String hostDestination = destinationUri.toLowerCase().startsWith("host:")
		? destinationUri.substring("host:".length()).trim()
		: destinationUri
	return getHostTargetPath(hostDestination)
}

final def airHelper = new NewAirPluginTool(args[0], args[1])
final Properties props = airHelper.getStepProperties()

String appId = props["applicationId"]?.trim()
String destinationForIastAgentFile = props["destinationForIastAgentFile"]?.trim()

if (appId == null || appId.trim().isEmpty()) {
	println "[Error] AS360 Application ID has not been specified."
	System.exit(1)
}

if (destinationForIastAgentFile == null || destinationForIastAgentFile.trim().isEmpty()) {
	println "[Error] Destination for IAST Agent file has not been specified."
	System.exit(1)
}

println "[Pre-check] Checking if Secagent.war already exists at destination '${destinationForIastAgentFile}'."
if (secagentWarExists(destinationForIastAgentFile)) {
	String deployedWarLocation = getDeployedWarLocation(destinationForIastAgentFile)
	println "[OK] Secagent.war already exists at ${deployedWarLocation}. Skipping entire operation."
	airHelper.setOutputProperty("DeployedIastAgentWar", deployedWarLocation)
	airHelper.storeOutputProperties()
	println "[OK] Deploy IAST Java Agent step finished."
	System.exit(0)
}

SCXRestClient restClient = new SCXRestClient(props)

String scanName = "IAST Java Agent " + new Date().format("yyyyMMdd-HHmmss")
String description = "IAST scan created by Deploy IAST Java Agent step"
String comment = "Created by Deploy IAST Java Agent step"

println "[Action] Step-1: Authentication with API key is handled by SCXRestClient initialization."

println "[Action] Step-2: Creating IAST scan for appId=${appId}."
Map scanStartResult = restClient.startIastScan(
	appId,
	scanName,
	description,
	true,
	"en-US",
	true,
	false,
	comment,
	43200,
	"Java")

String scanId = scanStartResult.scanId
String executionId = scanStartResult.executionId

airHelper.setOutputProperty("ScanId", scanId)
if (executionId) {
	airHelper.setOutputProperty("ExecutionId", executionId)
}

File workDirectory = new File("iast-agent-${scanId}")
println "[Action] Step-3: Downloading preconfigured IAST Java agent for scanId=${scanId}."
Map downloadResult = restClient.downloadIastAgentWithKey(scanId, workDirectory)
File agentZipFile = (File) downloadResult.zipFile
String downloadLink = downloadResult.downloadLink

if (downloadLink) {
	println "[OK] IAST agent download link: ${downloadLink}."
}
println "[OK] IAST agent ZIP is available at ${agentZipFile.absolutePath}."

println "[Action] Step-4: Extracting Secagent.war and deploying to '${destinationForIastAgentFile}'."
File extractedWar = extractSecagentWar(agentZipFile, new File(workDirectory, "extracted"))
String deployedWarLocation = deploySecagentWar(extractedWar, destinationForIastAgentFile)
println "[OK] Secagent.war deployment target: ${deployedWarLocation}."
airHelper.setOutputProperty("IastAgentZipFile", agentZipFile.absolutePath)
airHelper.setOutputProperty("DeployedIastAgentWar", deployedWarLocation)

println "[Action] Step-5: Checking scan execution status."
if (executionId == null || executionId.trim().isEmpty()) {
	println "[Warning] Execution ID is not available from scan creation response. Skipping execution status check."
}
else {
	def execution = restClient.getScanExecution(executionId)
	String status = execution?.Status ?: "Unknown"
	String executionProgress = execution?.ExecutionProgress ?: "Unknown"
	airHelper.setOutputProperty("ExecutionStatus", status)
	airHelper.setOutputProperty("ExecutionProgress", executionProgress)

	println "[OK] Scan execution '${executionId}' status='${status}', executionProgress='${executionProgress}'."

	if (status.equalsIgnoreCase("Failed") || executionProgress.equalsIgnoreCase("Failed")) {
		println "[Error] IAST scan execution entered a failed state."
		airHelper.storeOutputProperties()
		System.exit(1)
	}

	if (status.equalsIgnoreCase("Running") || executionProgress.equalsIgnoreCase("Running")) {
		println "[OK] IAST scan is running, which indicates it was configured and started successfully."
	}
	else {
		println "[Warning] IAST scan is not currently in Running state. Current status='${status}', executionProgress='${executionProgress}'."
	}
}

airHelper.storeOutputProperties()

println "[OK] Deploy IAST Java Agent step finished."
