package io.openems.common.bridge.http.dummy;

import static io.openems.common.bridge.http.dummy.DummyBridgeHttpFactory.dummyEndpointFetcher;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;

import io.openems.common.bridge.http.api.BridgeHttp;
import io.openems.common.bridge.http.api.BridgeHttp.Endpoint;
import io.openems.common.bridge.http.api.BridgeHttpFactory;
import io.openems.common.bridge.http.api.HttpError;
import io.openems.common.bridge.http.api.HttpResponse;

public record DummyBridgeHttpBundle(//
		DummyEndpointFetcher fetcher, //
		DummyBridgeHttpExecutor pool, //
		DummyBridgeHttpFactory bridgeFactory //
) {

	/**
	 * Creates a {@link DummyBridgeHttpBundle} with the given
	 * {@link DummyBridgeHttpExecutor}.
	 * 
	 * @param pool the {@link DummyBridgeHttpExecutor}
	 * @return the {@link DummyBridgeHttpBundle}
	 */
	public static DummyBridgeHttpBundle of(DummyBridgeHttpExecutor pool) {
		final var fetcher = dummyEndpointFetcher();
		return new DummyBridgeHttpBundle(fetcher, pool, DummyBridgeHttpFactory.ofBridgeImpl(() -> fetcher, () -> pool));
	}

	/**
	 * Creates a {@link DummyBridgeHttpBundle}.
	 *
	 * @return the {@link DummyBridgeHttpBundle}
	 */
	public static DummyBridgeHttpBundle of() {
		final var fetcher = dummyEndpointFetcher();
		final var pool = DummyBridgeHttpFactory.dummyBridgeHttpExecutor(true);
		return new DummyBridgeHttpBundle(fetcher, pool, DummyBridgeHttpFactory.ofBridgeImpl(() -> fetcher, () -> pool));
	}

	/**
	 * Sets a one time response which will be returned for the next http call.
	 * 
	 * @param response the {@link HttpResponse} to return
	 */
	public void forceNextSuccessfulResult(HttpResponse<String> response) {
		this.fetcher.addSingleUseEndpointHandler(t -> response);
	}

	/**
	 * Sets a one time error which will be thrown for the next http call.
	 * 
	 * @param error the {@link HttpError} to throw
	 */
	public void forceNextFailedResult(HttpError error) {
		this.fetcher.addSingleUseEndpointHandler(t -> {
			throw error;
		});
	}

	/**
	 * Executes all pending tasks synchronously.
	 */
	public void runTasksImmediately() {
		this.pool.update();
	}

	/**
	 * Creates a {@link EndpointExpect} which can be used to asynchronously check if
	 * a {@link BridgeHttp.Endpoint} was called.
	 * 
	 * <p>
	 * e. g.
	 * 
	 * <pre>
	 * // create listener for check
	 * final var wasCalled = dummyBridgeTestBundle.expect("http://your.url").toBeCalled();
	 * ...
	 * // trigger url call
	 * // depending on your component trigger event, set channel...
	 * ...
	 * // check if endpoint was called
	 * assertTrue("Endpoint was not called", wasCalled.get());
	 * </pre>
	 * 
	 * @param url only handles request to this url
	 * @return the {@link EndpointExpect} to check
	 */
	public EndpointExpect expect(String url) {
		return this.expect(t -> t.url().equals(url));
	}

	/**
	 * Creates a {@link EndpointExpect} which can be used to asynchronously check if
	 * a {@link BridgeHttp.Endpoint} was called.
	 * 
	 * <p>
	 * e. g.
	 * 
	 * <pre>
	 * // create listener for check
	 * final var wasCalled = dummyBridgeTestBundle.expect("http://your.url").toBeCalled();
	 * ...
	 * // trigger url call
	 * // depending on your component trigger event, set channel...
	 * ...
	 * // check if endpoint was called
	 * assertTrue("Endpoint was not called", wasCalled.get());
	 * </pre>
	 * 
	 * @param check only handles request where the predicate is true
	 * @return the {@link EndpointExpect} to check
	 */
	public EndpointExpect expect(Predicate<Endpoint> check) {
		final var request = new CompletableFuture<BridgeHttp.Endpoint>();
		final var result = new EndpointExpect(request);
		this.fetcher.addSingleUseEndpointHandler(t -> {
			if (!check.test(t)) {
				return null;
			}
			request.complete(t);
			return null;
		});
		return result;
	}

	/**
	 * Gets the {@link BridgeHttpFactory} of this test bundle.
	 * 
	 * @return the {@link BridgeHttpFactory}
	 */
	public BridgeHttpFactory factory() {
		return this.bridgeFactory;
	}

	public static class EndpointExpect {

		private final CompletableFuture<BridgeHttp.Endpoint> request;

		public EndpointExpect(CompletableFuture<BridgeHttp.Endpoint> request) {
			super();
			this.request = request;
		}

		/**
		 * Creates a check for the current {@link BridgeHttp.Endpoint} request if it got
		 * called or not.
		 * 
		 * @return a {@link AtomicBoolean} which will be set to true once the
		 *         {@link BridgeHttp.Endpoint} got called
		 */
		public AtomicBoolean toBeCalled() {
			final var result = new AtomicBoolean(false);
			this.request.thenAccept(e -> {
				result.set(true);
			});
			return result;
		}

	}

}
