package com.avaje.tests.query;

import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.PagedList;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;

import javax.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class TestQueryFindPagedList extends BaseTestCase {


  @Test(expected = PersistenceException.class)
  public void test_noMaxRows() throws ExecutionException, InterruptedException {

    Ebean.find(Order.class).findPagedList();
  }

  @Test
  public void test_maxRows_NoCount() throws ExecutionException, InterruptedException {

    ResetBasicData.reset();

    PagedList<Order> pagedList = Ebean.find(Order.class)
        .setMaxRows(4)
        .findPagedList();

    LoggedSqlCollector.start();

    List<Order> orders = pagedList.getList();

    assertTrue(!orders.isEmpty());
    List<String> loggedSql = LoggedSqlCollector.stop();

    assertEquals("Only 1 SQL statement, no count query",1, loggedSql.size());
  }


  @Test
  public void test_maxRows_countInBackground() throws ExecutionException, InterruptedException, TimeoutException {

    ResetBasicData.reset();

    PagedList<Order> pagedList = Ebean.find(Order.class)
        .setMaxRows(3)
        .findPagedList();

    Future<Integer> rowCount = pagedList.getFutureCount();
    List<Order> orders = pagedList.getList();

    // these are each getting the total row count
    int totalRowCount = pagedList.getTotalCount();
    Integer totalRowCountWithTimeout = rowCount.get(30, TimeUnit.SECONDS);
    Integer totalRowCountViaFuture = rowCount.get();

    assertTrue(orders.size() < totalRowCount);
    assertEquals(Integer.valueOf(totalRowCount), totalRowCountViaFuture);
    assertEquals(Integer.valueOf(totalRowCount), totalRowCountWithTimeout);
  }

  @Test
  public void test_maxRows_countInBackground_withLoadRowCount() throws InterruptedException {

    ResetBasicData.reset();

    // fetch less that total orders (page size 3)
    PagedList<Order> pagedList = Ebean.find(Order.class)
        .setMaxRows(3)
        .findPagedList();

    pagedList.loadCount();
    List<Order> orders = pagedList.getList();
    int totalRowCount = pagedList.getTotalCount();

    assertThat(orders.size()).isLessThan(totalRowCount);
    assertTrue(pagedList.hasNext());
    assertFalse(pagedList.hasPrev());


    // fetch less that total orders (page size 3)
    PagedList<Order> pagedList2 = Ebean.find(Order.class)
        .setFirstRow(1)
        .setMaxRows(3)
        .findPagedList();

    pagedList2.loadCount();
    List<Order> orders2 = pagedList2.getList();
    int totalRowCount2 = pagedList2.getTotalCount();
    assertTrue(pagedList2.hasNext());
    assertTrue(pagedList2.hasPrev());

    assertThat(totalRowCount).isEqualTo(totalRowCount2);
    assertThat(orders2.size()).isLessThan(totalRowCount);


    PagedList<Order> pagedList3 = Ebean.find(Order.class)
        .setFirstRow(2)
        .setMaxRows(150)
        .findPagedList();

    assertFalse(pagedList3.hasNext());
    assertTrue(pagedList3.hasPrev());

    List<Order> list3 = pagedList3.getList();
    String xtoYofZ = pagedList3.getDisplayXtoYofZ(" to ", " of ");
    assertThat(xtoYofZ).isEqualTo("3 to "+totalRowCount+" of "+totalRowCount);
    assertThat(list3.size()).isEqualTo(totalRowCount - 2);

    PagedList<Order> pagedList4 = Ebean.find(Order.class)
        .setFirstRow(0)
        .setMaxRows(totalRowCount)
        .findPagedList();

    assertFalse(pagedList4.hasNext());
    assertFalse(pagedList4.hasPrev());
    assertThat(pagedList4.getDisplayXtoYofZ(" to ", " of ")).isEqualTo("1 to "+totalRowCount+" of "+totalRowCount);

    PagedList<Order> pagedList5 = Ebean.find(Order.class)
        .setFirstRow(0)
        .setMaxRows(totalRowCount - 1)
        .findPagedList();

    assertTrue(pagedList5.hasNext());
    assertFalse(pagedList5.hasPrev());
  }

  @Test
  public void test_noCount() throws ExecutionException, InterruptedException {

    ResetBasicData.reset();

    PagedList<Order> pagedList = Ebean.find(Order.class).setMaxRows(4).findPagedList();

    LoggedSqlCollector.start();

    List<Order> orders = pagedList.getList();

    assertTrue(!orders.isEmpty());
    List<String> loggedSql = LoggedSqlCollector.stop();

    assertEquals("Only 1 SQL statement, no count query",1, loggedSql.size());
  }

  @Test
  public void test_countInBackground() throws ExecutionException, InterruptedException, TimeoutException {

    ResetBasicData.reset();

    PagedList<Order> pagedList = Ebean.find(Order.class).setMaxRows(3).findPagedList();

    Future<Integer> rowCount = pagedList.getFutureCount();
    List<Order> orders = pagedList.getList();

    // these are each getting the total row count
    int totalRowCount = pagedList.getTotalCount();
    Integer totalRowCountWithTimeout = rowCount.get(30, TimeUnit.SECONDS);
    Integer totalRowCountViaFuture = rowCount.get();

    assertTrue(orders.size() < totalRowCount);
    assertEquals(Integer.valueOf(totalRowCount), totalRowCountViaFuture);
    assertEquals(Integer.valueOf(totalRowCount), totalRowCountWithTimeout);
  }


  @Test
  public void test_countInBackground_withLoadRowCount() throws InterruptedException {

    ResetBasicData.reset();

    // fetch less that total orders (page size 3)
    PagedList<Order> pagedList = Ebean.find(Order.class).setMaxRows(3).findPagedList();

    pagedList.loadCount();
    List<Order> orders = pagedList.getList();
    int totalRowCount = pagedList.getTotalCount();

    assertThat(orders.size()).isLessThan(totalRowCount);
  }


  @Test
  public void test_countUsingForegound() throws ExecutionException, InterruptedException {

    ResetBasicData.reset();

    PagedList<Order> pagedList = Ebean.find(Order.class).setMaxRows(6).findPagedList();

    LoggedSqlCollector.start();

    // kinda not normal but just wrap in a transaction to assert
    // the background fetch does not occur (which explicitly creates
    // its own transaction) ... so a bit naughty with the test here
    Ebean.beginTransaction();
    try {

      List<Order> orders = pagedList.getList();
      int totalRowCount = pagedList.getTotalCount();

      // invoke it again but cached...
      int totalRowCountAgain = pagedList.getTotalCount();

      List<String> loggedSql = LoggedSqlCollector.stop();

      assertTrue(orders.size() < totalRowCount);
      assertEquals(2, loggedSql.size());
      assertEquals(totalRowCount, totalRowCountAgain);

      String firstTxn = loggedSql.get(0).substring(0, 10);
      String secTxn = loggedSql.get(1).substring(0, 10);

      assertEquals(firstTxn, secTxn);

    } finally {
      Ebean.endTransaction();
    }
  }


  @Test
  public void test_usingAlias() throws ExecutionException, InterruptedException {

    ResetBasicData.reset();

    PagedList<Order> pagedList = Ebean.find(Order.class)
        .alias("b")
        .where().raw("b.id > 0")
        .setMaxRows(6)
        .findPagedList();

    LoggedSqlCollector.start();

    pagedList.getTotalCount();
    pagedList.getList();

    List<String> loggedSql = LoggedSqlCollector.stop();

    assertEquals(2, loggedSql.size());
    assertThat(loggedSql.get(0)).contains("select count(*) from o_order b where b.id > 0");
    assertThat(trimSql(loggedSql.get(1), 3)).contains("select b.id, b.status, b.order_date");
  }
}
