package de.renew.plugin.command;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UncheckedIOException;

import io.vavr.control.Try;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class CommandHelper {

    static String doExecuteSuccessfully(CLCommand cmd, final String... args) {
        final CommandHelper.Result result = CommandHelper.doExecute(cmd, args);
        if (result.run.isFailure()) {
            result.run.getCause().printStackTrace();
        }
        assertTrue(result.run.isSuccess(), "Run was not Successful.");
        return result.output;
    }

    static <T extends Throwable> CommandHelper.Result doExecuteUnsuccessfully(
        CLCommand cmd, Class<T> exception, final String... args)
    {
        final CommandHelper.Result result = CommandHelper.doExecute(cmd, args);
        assertTrue(result.run.isFailure());
        assertEquals(exception, result.run.getCause().getClass());
        return result;
    }

    static void assertString(final String expected, final String actual) {
        final String expectedTrimmed = expected.replaceAll("[\\r\\n]", "").trim();
        final String actualTrimmed = actual.replaceAll("[\\r\\n]", "").trim();
        assertEquals(expectedTrimmed, actualTrimmed);
    }

    static Result doExecute(CLCommand command, String... args) {
        return doExecute((ps) -> command.execute(args, ps));
    }

    /**
     * Provides the information of the run.
     * The command-classes are rather hard to test since they do not return a value,
     * so it is required to check weather or not the value in the print stream has accepted the right values.
     * Creating Streams is a task which has to be done right.
     * Furthermore, we want to check the value and the respective exepctions seperately, therefore this mehtod returns
     * the {@link Result} which allows to check both values.
     * @param execute the labmda which accepts a {@link PrintStream}
     * @return The result which holds the run information of the lambda and the String which was created out of the {@link PrintStream}
     */
    private static Result doExecute(Execute execute) {
        try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            try (final PrintStream ps = new PrintStream(out)) {
                final Try<Void> run = Try.run(() -> execute.run(ps));
                out.flush();
                return new Result(run, out.toString(UTF_8));
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    interface Execute {
        void run(PrintStream ps) throws IOException;
    }

    /**
         * Holds the run information which were created by executing the CLCommand.
         */
    record Result(Try<Void> run, String output) {
    }
}
