package au.com.agic.apptesting.utils.impl;

import au.com.agic.apptesting.constants.Constants;
import au.com.agic.apptesting.exception.FileProfileAccessException;
import au.com.agic.apptesting.exception.RemoteFeatureException;
import au.com.agic.apptesting.utils.FeatureFileUtils;
import au.com.agic.apptesting.utils.FeatureReader;
import io.vavr.control.Try;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * An implementation of the feature files utils service
 */
public class FeatureFileUtilsImpl implements FeatureFileUtils {

	private static final String FEATURE_EXT = ".feature";
	private static final FeatureReader FEATURE_READER = new FeatureReaderImpl();

	@Override
	public List<FileDetails> getFeatureScripts(
		@NotNull final String path,
		final String featureGroup,
		final String baseUrl) {
		checkArgument(StringUtils.isNotBlank(path));

		/*
			Assume a null base url is just an empty string
		 */
		final String fixedBaseUrl = StringUtils.defaultIfBlank(baseUrl, "");
		final String fixedPath = fixedBaseUrl + path;

		/*
			We need to be wary of different OSs throwing exceptions working with
			urls in these file methods.
		 */
		return Try.of(() -> {
			if (Files.isDirectory(Paths.get(fixedPath))) {
				/*
					We know this is a local directory, so process the files.
					We only return files that match the feature group header
				 */
				return processLocalFiles(fixedPath).stream()
					.filter(e -> FEATURE_READER.selectFile(e, featureGroup))
					.map(e -> new FileDetails(e, true))
					.collect(Collectors.toList());

			}

			if (Files.isRegularFile(Paths.get(fixedPath)) || Files.isRegularFile(Paths.get("./" + fixedPath))) {
				/*
					We know this is a single file, so just return it. Note that we
					ignore the supplied feature group when we are looking at
					a single file
				 */
				return Arrays.asList(new FileDetails(new File(path), true));
			}

			throw new Exception();
		})
		/*
			Either there was an exception because the url was not accepted in the
			file operations, or we correctly determine that the url is not a
			file or directory
		 */
		.orElse(Try.of(() -> processRemoteUrl(fixedPath).stream()
			.map(e -> new FileDetails(e, false))
			.collect(Collectors.toList()))
		)
		/*
			This wasn't a url or a file, so we just have to throw an exception
		 */
		.getOrElseThrow(
			ex -> new FileProfileAccessException("Error attempting to open \"" + path
				+ "\". Make sure the supplied path or URL was correct.", ex)
		);
	}

	private List<File> processRemoteUrl(@NotNull final String path) throws IOException {
		final File copy = File.createTempFile("webapptester", ".feature");

		try {
			final RetryTemplate template = new RetryTemplate();
			final SimpleRetryPolicy policy = new SimpleRetryPolicy();
			policy.setMaxAttempts(Constants.URL_COPY_RETRIES);
			template.setRetryPolicy(policy);
			template.execute(context -> {
				FileUtils.copyURLToFile(new URL(path), copy);
				return null;
			});

			return Arrays.asList(copy);
		} catch (final FileNotFoundException ex) {
			/*
				Don't leave an empty file hanging around
			 */
			FileUtils.deleteQuietly(copy);
			throw new RemoteFeatureException("The remote file could not be downloaded."
				+ " Either the URL was invalid, or the path was actually supposed to reference a"
				+ " local file but that file could not be found an so was assumed to be a URL.",  ex);
		}
	}

	private List<File> processLocalFiles(@NotNull final String path) {
		final List<File> features = new ArrayList<>();
		loopOverFiles(Paths.get(path).toFile(), features, new ArrayList<>());
		return features;
	}

	private void loopOverFiles(
		@NotNull final File directory,
		@NotNull final List<File> features,
		@NotNull final List<String> processed) {
		checkNotNull(directory);
		checkNotNull(features);
		checkNotNull(processed);

		if (!processed.contains(directory.toString())) {
			processed.add(directory.toString());
			if (directory.isDirectory()) {
				final File[] files = directory.listFiles();

				if (files != null) {
					for (final File file : files) {
						if (file.isDirectory()) {
							loopOverFiles(file, features, processed);
						} else {
							if (file.getName().endsWith(FEATURE_EXT)) {
								features.add(file);
							}
						}
					}
				}
			}
		}
	}
}