/*
 * Copyright 2021-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.opentest4j.reporting.tooling.core.htmlreport;

import org.apiguardian.api.API;
import org.opentest4j.reporting.events.core.Infrastructure;
import org.opentest4j.reporting.events.core.Result;
import org.opentest4j.reporting.events.java.FileEncoding;
import org.opentest4j.reporting.events.java.HeapSize;
import org.opentest4j.reporting.events.java.JavaVersion;
import org.opentest4j.reporting.events.java.Throwable;
import org.opentest4j.reporting.tooling.spi.htmlreport.Contributor;
import org.opentest4j.reporting.tooling.spi.htmlreport.KeyValuePairs;
import org.opentest4j.reporting.tooling.spi.htmlreport.PreFormattedOutput;
import org.opentest4j.reporting.tooling.spi.htmlreport.Section;
import org.opentest4j.reporting.tooling.spi.htmlreport.Subsections;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

import static org.apiguardian.api.API.Status.INTERNAL;
import static org.opentest4j.reporting.tooling.core.htmlreport.CoreContributor.addToTable;
import static org.opentest4j.reporting.tooling.core.util.DomUtils.findChild;
import static org.opentest4j.reporting.tooling.core.util.DomUtils.findChildren;
import static org.opentest4j.reporting.tooling.core.util.DomUtils.getAttributeValue;

/**
 * Contributes sections to the HTML report elements in the java schema.
 *
 * @since 0.2.0
 */
@API(status = INTERNAL, since = "0.2.0")
public class JavaContributor implements Contributor {

	/**
	 * Create a new instance.
	 */
	public JavaContributor() {
	}

	@Override
	public List<Section> contributeSectionsForExecution(Context context) {
		var sections = new ArrayList<Section>();
		findChild(context.element(), Infrastructure.ELEMENT) //
				.flatMap(JavaContributor::createJvmSection) //
				.ifPresent(sections::add);
		return sections;
	}

	@Override
	public List<Section> contributeSectionsForTestNode(Context context) {
		var sections = new ArrayList<Section>();
		createResultSection(context.element()).ifPresent(sections::add);
		return sections;
	}

	private static Optional<Section> createJvmSection(Node infrastructure) {
		var table = new LinkedHashMap<String, String>();
		addToTable(infrastructure, JavaVersion.ELEMENT, "Java version", table::put);
		addToTable(infrastructure, FileEncoding.ELEMENT, "File encoding", table::put);
		findChild(infrastructure, HeapSize.ELEMENT) //
				.flatMap(heapSize -> getAttributeValue(heapSize, HeapSize.MAX)) //
				.map(maxHeapSize -> String.format("%s (%s bytes)", toHumanReadableFormat(Long.parseLong(maxHeapSize)),
					maxHeapSize)) //
				.ifPresent(maxHeapSize -> table.put("Max heap size", maxHeapSize));

		if (table.isEmpty()) {
			return Optional.empty();
		}

		var keyValuePairs = KeyValuePairs.builder().content(table).build();
		return Optional.of(Section.builder().title("Java Virtual Machine").order(10).addBlock(keyValuePairs).build());
	}

	private static Optional<Section> createResultSection(Element element) {
		return findChild(element, Result.ELEMENT) //
				.map(result -> findChildren(result, Throwable.ELEMENT) //
						.map(throwable -> {
							var type = getAttributeValue(throwable, Throwable.TYPE).orElse("Throwable");
							var stackTrace = throwable.getTextContent();
							var preFormattedOutput = PreFormattedOutput.builder().content(stackTrace).build();
							return Section.builder().title(type).addBlock(preFormattedOutput).build();
						}) //
						.toList() //
				) //
				.filter(content -> !content.isEmpty()) //
				.map(content -> Subsections.builder().content(content).build()) //
				.map(subsections -> Section.builder().title("Result").order(25).addBlock(subsections).build());
	}

	// Adapted from https://www.baeldung.com/java-human-readable-byte-size#using-the-longnumberofleadingzeros-method
	private static String toHumanReadableFormat(long size) {
		if (size < 1024)
			return size + " B";
		int unitIdx = (63 - Long.numberOfLeadingZeros(size)) / 10;
		return formatSize(size, 1L << (unitIdx * 10), " KMGTPE".charAt(unitIdx) + "iB");
	}

	private static String formatSize(long size, long divider, String unitName) {
		return new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.US)).format((double) size / divider) + " "
				+ unitName;
	}
}
