/*
 * Copyright 2015-2023 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.platform.reporting.open.xml;

import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.io.CleanupMode.ON_SUCCESS;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId;
import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request;
import static org.junit.platform.launcher.core.LauncherFactoryForTestingPurposesOnly.createLauncher;
import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.ENABLED_PROPERTY_NAME;
import static org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener.OUTPUT_DIR_PROPERTY_NAME;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.platform.engine.TestEngine;
import org.junit.platform.engine.UniqueId;
import org.junit.platform.engine.reporting.ReportEntry;
import org.junit.platform.engine.support.hierarchical.DemoHierarchicalTestEngine;
import org.opentest4j.reporting.tooling.validator.DefaultValidator;
import org.opentest4j.reporting.tooling.validator.ValidationResult;
import org.xmlunit.assertj3.XmlAssert;
import org.xmlunit.placeholder.PlaceholderDifferenceEvaluator;

/**
 * Tests for {@link OpenTestReportGeneratingListener}.
 *
 * @since 1.9
 */
public class OpenTestReportGeneratingListenerTests {

	@TempDir(cleanup = ON_SUCCESS)
	Path tempDirectory;

	@Test
	void writesValidXmlReport() throws Exception {
		var engine = new DemoHierarchicalTestEngine("dummy");
		engine.addTest("failingTest", "display<-->Name 😎", (context, descriptor) -> {
			var listener = context.request.getEngineExecutionListener();
			listener.reportingEntryPublished(descriptor, ReportEntry.from("key", "value"));
			fail("failure message");
		});

		executeTests(engine);

		var xmlFile = findXmlReport();
		assertThat(validate(xmlFile)).isEmpty();

		var expected = """
				<e:events xmlns="https://schemas.opentest4j.org/reporting/core/0.1.0" xmlns:e="https://schemas.opentest4j.org/reporting/events/0.1.0" xmlns:java="https://schemas.opentest4j.org/reporting/java/0.1.0"
				          xmlns:junit="https://schemas.junit.org/open-test-reporting" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
				          xsi:schemaLocation="https://schemas.junit.org/open-test-reporting https://junit.org/junit5/schemas/open-test-reporting/junit-1.9.xsd">
				    <infrastructure>
				        <hostName>${xmlunit.ignore}</hostName>
				        <userName>${xmlunit.ignore}</userName>
				        <operatingSystem>${xmlunit.ignore}</operatingSystem>
				        <cpuCores>${xmlunit.ignore}</cpuCores>
				        <java:javaVersion>${xmlunit.ignore}</java:javaVersion>
				        <java:fileEncoding>${xmlunit.ignore}</java:fileEncoding>
				        <java:heapSize max="${xmlunit.isNumber}"/>
				    </infrastructure>
				    <e:started id="1" name="dummy" time="${xmlunit.isDateTime}">
				        <metadata>
				            <junit:uniqueId>[engine:dummy]</junit:uniqueId>
				            <junit:legacyReportingName>dummy</junit:legacyReportingName>
				            <junit:type>CONTAINER</junit:type>
				        </metadata>
				    </e:started>
				    <e:started id="2" name="display&lt;--&gt;Name 😎" parentId="1" time="${xmlunit.isDateTime}">
				        <metadata>
				            <junit:uniqueId>[engine:dummy]/[test:failingTest]</junit:uniqueId>
				            <junit:legacyReportingName>display&lt;--&gt;Name 😎</junit:legacyReportingName>
				            <junit:type>TEST</junit:type>
				        </metadata>
				    </e:started>
				    <e:reported id="2" time="${xmlunit.isDateTime}">
				        <attachments>
				            <data time="${xmlunit.isDateTime}">
				                <entry key="key">value</entry>
				            </data>
				        </attachments>
				    </e:reported>
				    <e:finished id="2" time="${xmlunit.isDateTime}">
				        <result status="FAILED">
				            <java:throwable assertionError="true" type="org.opentest4j.AssertionFailedError">
				                ${xmlunit.matchesRegex(org\\.opentest4j\\.AssertionFailedError: failure message)}
				            </java:throwable>
				        </result>
				    </e:finished>
				    <e:finished id="1" time="${xmlunit.isDateTime}">
				        <result status="SUCCESSFUL"/>
				    </e:finished>
				</e:events>
				""";

		XmlAssert.assertThat(xmlFile).and(expected) //
				.withDifferenceEvaluator(new PlaceholderDifferenceEvaluator()) //
				.ignoreWhitespace() //
				.areIdentical();
	}

	private ValidationResult validate(Path xmlFile) throws URISyntaxException {
		var catalogUri = requireNonNull(getClass().getResource("catalog.xml")).toURI();
		return new DefaultValidator(catalogUri).validate(xmlFile);
	}

	private void executeTests(TestEngine engine) {
		var build = request() //
				.selectors(selectUniqueId(UniqueId.forEngine(engine.getId()))) //
				.configurationParameter(ENABLED_PROPERTY_NAME, String.valueOf(true)) //
				.configurationParameter(OUTPUT_DIR_PROPERTY_NAME, tempDirectory.toString()) //
				.build();
		createLauncher(engine).execute(build, new OpenTestReportGeneratingListener());
	}

	private Path findXmlReport() throws IOException {
		try (var stream = Files.list(tempDirectory)) {
			return stream.findAny().orElseThrow(AssertionError::new);
		}
	}

}
