/* Hibernate, Relational Persistence for Idiomatic Java
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 * Copyright: Red Hat Inc. and Hibernate Authors
 */
package org.hibernate.reactive.configuration;

import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.Timeout;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.hibernate.reactive.provider.Settings;
import org.hibernate.reactive.containers.DatabaseConfiguration;
import org.hibernate.reactive.containers.DatabaseConfiguration.DBType;
import org.hibernate.reactive.pool.ReactiveConnectionPool;
import org.hibernate.reactive.pool.impl.SqlClientPool;
import org.hibernate.reactive.testing.TestingRegistryRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;

import static org.junit.Assume.assumeTrue;

@RunWith(VertxUnitRunner.class)
public class ReactiveConnectionPoolTest {

	@Rule
	public Timeout rule = Timeout.seconds( 3600 );

	@Rule
	public ExpectedException thrown = ExpectedException.none();

	@Rule
	public TestingRegistryRule registryRule = new TestingRegistryRule();

	protected static void test(TestContext context, CompletionStage<?> cs) {
		// this will be added to TestContext in the next vert.x release
		Async async = context.async();
		cs.whenComplete( (res, err) -> {
			if ( err != null ) {
				context.fail( err );
			}
			else {
				async.complete();
			}
		} );
	}

	private ReactiveConnectionPool configureAndStartPool(Map<String, Object> config) {
		SqlClientPool reactivePool = new SqlClientPool();
		reactivePool.injectServices( registryRule.getServiceRegistry() );
		reactivePool.configure( config );
		reactivePool.start();
		return reactivePool;
	}

	@Test
	public void configureWithJdbcUrl(TestContext context) {
		// This test doesn't need to rotate across all DBs and has PG-specific logic in it
		assumeTrue( DatabaseConfiguration.dbType() == DBType.POSTGRESQL );

		String url = DatabaseConfiguration.getJdbcUrl();
		Map<String,Object> config = new HashMap<>();
		config.put( Settings.URL, url );
		ReactiveConnectionPool reactivePool = configureAndStartPool( config );
		verifyConnectivity( context, reactivePool );
	}

	@Test
	public void configureWithCredentials(TestContext context) {
		// This test doesn't need to rotate across all DBs and has PG-specific logic in it
		assumeTrue( DatabaseConfiguration.dbType() == DBType.POSTGRESQL );

		// Set up URL with invalid credentials so we can ensure that
		// explicit USER and PASS settings take precedence over credentials in the URL
		String url = DatabaseConfiguration.getJdbcUrl();
		url = url.replace( "user=" + DatabaseConfiguration.USERNAME, "user=bogus" );
		url = url.replace( "password=" + DatabaseConfiguration.PASSWORD, "password=bogus" );

		// Correct user/password are supplied explicitly in the config map and
		// should override the credentials in the URL
		Map<String,Object> config = new HashMap<>();
		config.put( Settings.URL, url );
		config.put( Settings.USER, DatabaseConfiguration.USERNAME );
		config.put( Settings.PASS, DatabaseConfiguration.PASSWORD );
		ReactiveConnectionPool reactivePool = configureAndStartPool( config );
		verifyConnectivity( context, reactivePool );
	}

	@Test
	public void configureWithWrongCredentials(TestContext context) {
		// This test doesn't need to rotate across all DBs and has PG-specific logic in it
		assumeTrue( DatabaseConfiguration.dbType() == DBType.POSTGRESQL );

		thrown.expect( CompletionException.class );
		thrown.expectMessage( "io.vertx.pgclient.PgException:" );
		thrown.expectMessage( "\"bogus\"" );

		String url = DatabaseConfiguration.getJdbcUrl();
		Map<String,Object> config = new HashMap<>();
		config.put( Settings.URL, url );
		config.put( Settings.USER, "bogus" );
		config.put( Settings.PASS, "bogus" );
		ReactiveConnectionPool reactivePool = configureAndStartPool( config );
		verifyConnectivity( context, reactivePool );
	}

	private void verifyConnectivity(TestContext context, ReactiveConnectionPool reactivePool) {
		test( context, reactivePool.getConnection().thenCompose(
				connection -> connection.select( "SELECT 1")
						.thenApply( rows -> {
							context.assertNotNull( rows );
							context.assertEquals( 1, rows.size() );
							Object[] row = rows.next();
							context.assertEquals( 1, row[0] );
							return null;
						} ) ) );
	}

}