package com.j256.ormlite.jdbc;

import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.or;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;

import org.junit.Test;

import com.j256.ormlite.BaseJdbcTest;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.stmt.GenericRowMapper;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.support.GeneratedKeyHolder;
import com.j256.ormlite.table.DatabaseTable;

public class JdbcDatabaseConnectionTest extends BaseJdbcTest {

	private static final String FOO_TABLE_NAME = "foo";
	private static final String FOOINT_TABLE_NAME = "fooint";
	private static final String FOOSTRING_TABLE_NAME = "foostring";
	private static final String FOONOTGENERATEDID_TABLE_NAME = "foonotgeneratedid";

	@Test
	public void testQueryForLong() throws Exception {
		DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(FOO_TABLE_NAME);
		try {
			Dao<Foo, Object> dao = createDao(Foo.class, true);
			Foo foo = new Foo();
			long id = 21321321L;
			foo.id = id;
			assertEquals(1, dao.create(foo));

			StringBuilder sb = new StringBuilder();
			sb.append("select ");
			databaseType.appendEscapedEntityName(sb, "id");
			sb.append(" from foo");
			assertEquals(id, databaseConnection.queryForLong(sb.toString()));
		} finally {
			connectionSource.releaseConnection(databaseConnection);
		}
	}

	@Test(expected = SQLException.class)
	public void testQueryForLongNoResult() throws Exception {
		DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(FOO_TABLE_NAME);
		try {
			createDao(Foo.class, true);

			StringBuilder sb = new StringBuilder();
			sb.append("select ");
			databaseType.appendEscapedEntityName(sb, "id");
			sb.append(" from foo");
			databaseConnection.queryForLong(sb.toString());
		} finally {
			connectionSource.releaseConnection(databaseConnection);
		}
	}

	@Test(expected = SQLException.class)
	public void testQueryForLongTooManyResults() throws Exception {
		DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(FOO_TABLE_NAME);
		try {
			Dao<Foo, Object> dao = createDao(Foo.class, true);
			Foo foo = new Foo();
			long id = 21321321L;
			foo.id = id;
			// insert twice
			assertEquals(1, dao.create(foo));
			assertEquals(1, dao.create(foo));

			StringBuilder sb = new StringBuilder();
			sb.append("select ");
			databaseType.appendEscapedEntityName(sb, "id");
			sb.append(" from foo");
			databaseConnection.queryForLong(sb.toString());
		} finally {
			connectionSource.releaseConnection(databaseConnection);
		}
	}

	@Test
	public void testUpdateReleaseConnection() throws Exception {
		Connection connection = createMock(Connection.class);
		JdbcDatabaseConnection jdc = new JdbcDatabaseConnection(connection);
		String statement = "statement";
		PreparedStatement prepStmt = createMock(PreparedStatement.class);
		expect(connection.prepareStatement(statement)).andReturn(prepStmt);
		expect(prepStmt.executeUpdate()).andReturn(1);
		// should close the statement
		prepStmt.close();
		connection.close();
		replay(connection, prepStmt);
		jdc.update(statement, new Object[0], new FieldType[0]);
		jdc.close();
		verify(connection, prepStmt);
	}

	@Test
	public void testInsertReleaseConnection() throws Exception {
		Connection connection = createMock(Connection.class);
		PreparedStatement prepStmt = createMock(PreparedStatement.class);
		GeneratedKeyHolder keyHolder = createMock(GeneratedKeyHolder.class);
		ResultSet resultSet = createMock(ResultSet.class);
		ResultSetMetaData metaData = createMock(ResultSetMetaData.class);

		JdbcDatabaseConnection jdc = new JdbcDatabaseConnection(connection);
		String statement = "statement";
		expect(keyHolder.getColumnName()).andReturn("id");
		expect(connection.prepareStatement(eq(statement), aryEq(new String[]{"id"}))).andReturn(prepStmt);
		expect(prepStmt.executeUpdate()).andReturn(1);
		expect(prepStmt.getGeneratedKeys()).andReturn(resultSet);
		expect(resultSet.getMetaData()).andReturn(metaData);
		expect(resultSet.next()).andReturn(true);
		expect(metaData.getColumnCount()).andReturn(1);
		expect(metaData.getColumnType(1)).andReturn(Types.INTEGER);
		int keyHolderVal = 123131;
		expect(resultSet.getInt(1)).andReturn(keyHolderVal);
		keyHolder.addKey(keyHolderVal);
		expect(resultSet.next()).andReturn(false);
		// should close the statement
		prepStmt.close();
		connection.close();
		replay(connection, prepStmt, keyHolder, resultSet, metaData);
		jdc.insert(statement, new Object[0], new FieldType[0], keyHolder);
		jdc.close();
		verify(connection, prepStmt, keyHolder, resultSet, metaData);
	}

	@Test
	public void testQueryForOneReleaseConnection() throws Exception {
		Connection connection = createMock(Connection.class);
		PreparedStatement prepStmt = createMock(PreparedStatement.class);
		GeneratedKeyHolder keyHolder = createMock(GeneratedKeyHolder.class);
		ResultSet resultSet = createMock(ResultSet.class);
		@SuppressWarnings("unchecked")
		GenericRowMapper<Foo> rowMapper = createMock(GenericRowMapper.class);

		JdbcDatabaseConnection jdc = new JdbcDatabaseConnection(connection);
		String statement = "statement";
		expect(connection.prepareStatement(statement, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY))
				.andReturn(prepStmt);
		expect(prepStmt.executeQuery()).andReturn(resultSet);
		expect(resultSet.getMetaData()).andReturn(null);
		expect(resultSet.next()).andReturn(false);
		resultSet.close();
		expect(prepStmt.getMoreResults()).andReturn(false);
		// should close the statement
		prepStmt.close();
		connection.close();
		replay(connection, prepStmt, keyHolder, resultSet, rowMapper);
		jdc.queryForOne(statement, new Object[0], new FieldType[0], rowMapper, null);
		jdc.close();
		verify(connection, prepStmt, keyHolder, resultSet, rowMapper);
	}

	@Test
	public void testQueryKeyHolderNoKeys() throws Exception {
		DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(FOO_TABLE_NAME);
		try {
			createDao(Foo.class, true);
			GeneratedKeyHolder keyHolder = createMock(GeneratedKeyHolder.class);
			expect(keyHolder.getColumnName()).andReturn("id");
			keyHolder.addKey(or(eq(0), eq(0L))); // Depends on driver
			replay(keyHolder);
			StringBuilder sb = new StringBuilder();
			sb.append("insert into foo (");
			databaseType.appendEscapedEntityName(sb, "id");
			sb.append(") values (2)");
			databaseConnection.insert(sb.toString(), new Object[0], new FieldType[0], keyHolder);
			verify(keyHolder);
		} finally {
			connectionSource.releaseConnection(databaseConnection);
		}
	}

	@Test
	public void testIdColumnInteger() throws Exception {
		// NOTE: this doesn't seem to generate an INTEGER type, oh well
		DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(FOOINT_TABLE_NAME);
		try {
			createDao(FooInt.class, true);
			GeneratedKeyHolder keyHolder = createMock(GeneratedKeyHolder.class);
			expect(keyHolder.getColumnName()).andReturn("id");
			keyHolder.addKey(or(eq(2), eq(2L))); // Depends on driver
			replay(keyHolder);
			StringBuilder sb = new StringBuilder();
			sb.append("insert into fooint (");
			databaseType.appendEscapedEntityName(sb, "id");
			sb.append(") values (2)");
			databaseConnection.insert(sb.toString(), new Object[0], new FieldType[0], keyHolder);
			verify(keyHolder);
		} finally {
			connectionSource.releaseConnection(databaseConnection);
		}
	}

	@Test
	public void testIdColumnInvalid() throws Exception {
		// NOTE: this doesn't seem to generate an INTEGER type, oh well
		DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(FOOINT_TABLE_NAME);
		try {
			createDao(FooInt.class, true);
			GeneratedKeyHolder keyHolder = createMock(GeneratedKeyHolder.class);
			expect(keyHolder.getColumnName()).andReturn("id");
			keyHolder.addKey(or(eq(1), eq(1L))); // Depends on driver
			replay(keyHolder);
			StringBuilder sb = new StringBuilder();
			sb.append("insert into fooint (");
			databaseType.appendEscapedEntityName(sb, "stuff");
			sb.append(") values ('zipper')");
			databaseConnection.insert(sb.toString(), new Object[0], new FieldType[0], keyHolder);
			verify(keyHolder);
		} finally {
			connectionSource.releaseConnection(databaseConnection);
		}
	}

	@Test
	public void testIdColumnChangedFromStringToNumber() throws Exception {
		// NOTE: trying to get the database to return a string as a result but could not figure it out
		DatabaseConnection databaseConnection = connectionSource.getReadOnlyConnection(FOOSTRING_TABLE_NAME);
		try {
			createDao(FooString.class, true);
			GeneratedKeyHolder keyHolder = createMock(GeneratedKeyHolder.class);
			expect(keyHolder.getColumnName()).andReturn("id");
			keyHolder.addKey(or(eq(0), eq(0L))); // Depends on driver
			replay(keyHolder);
			StringBuilder sb = new StringBuilder();
			sb.append("insert into foostring (");
			databaseType.appendEscapedEntityName(sb, "id");
			sb.append(", ");
			databaseType.appendEscapedEntityName(sb, "stuff");
			sb.append(") values ('12', 'zipper')");
			databaseConnection.insert(sb.toString(), new Object[0], new FieldType[0], keyHolder);
			verify(keyHolder);
		} finally {
			connectionSource.releaseConnection(databaseConnection);
		}
	}

	@Test(expected = SQLException.class)
	public void testGeneratedIdNoReturn() throws Exception {
		createDao(FooNotGeneratedId.class, true);
		Dao<FooInt, Object> genDao = createDao(FooInt.class, false);
		FooInt foo = new FooInt();
		foo.stuff = "hello";
		genDao.create(foo);
	}

	/* =================================================================================================== */

	@DatabaseTable(tableName = FOO_TABLE_NAME)
	protected static class Foo {
		@DatabaseField
		public long id;

		Foo() {
		}
	}

	@DatabaseTable(tableName = FOOINT_TABLE_NAME)
	protected static class FooInt {
		@DatabaseField(generatedId = true)
		public int id;
		@DatabaseField
		public String stuff;

		FooInt() {
		}
	}

	@DatabaseTable(tableName = FOOSTRING_TABLE_NAME)
	protected static class FooString {
		@DatabaseField(id = true)
		public String id;
		@DatabaseField
		public String stuff;

		FooString() {
		}
	}

	@DatabaseTable(tableName = FOONOTGENERATEDID_TABLE_NAME)
	protected static class FooNotGeneratedId {
		@DatabaseField
		public int id;
		@DatabaseField
		public String stuff;

		FooNotGeneratedId() {
		}
	}
}