package com.avaje.tests.readaudit;

import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.EbeanServerFactory;
import com.avaje.ebean.FutureList;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheStatistics;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.event.readaudit.ReadAuditQueryPlan;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.tests.model.basic.Country;
import com.avaje.tests.model.basic.EBasicChangeLog;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicInteger;

import static org.assertj.core.api.Assertions.assertThat;

public class TestReadAudit extends BaseTestCase {

  TDReadAuditPrepare readAuditPrepare = new TDReadAuditPrepare(true);

  TDReadAuditLogger readAuditLogger = new TDReadAuditLogger(true);

  SpiEbeanServer server;

  Long id1;
  Long id2;

  @Before
  public void setup() {
    server = getServer();

    EBasicChangeLog bean = new EBasicChangeLog();
    bean.setName("readAudito1");
    bean.setShortDescription("readAudit hello");
    server.save(bean);
    id1 = bean.getId();

    EBasicChangeLog bean2 = new EBasicChangeLog();
    bean2.setName("readAudito2");
    bean2.setShortDescription("readAudit hi");
    server.save(bean2);
    id2 = bean2.getId();

    Country ar = new Country();
    ar.setCode("AR");
    ar.setName("Argentina");
    server.save(ar);
  }

  @After
  public void shutdown() {
    server.shutdown(true, false);
  }

  @Test
  public void test_findById() {
    resetCounters();

    EBasicChangeLog found = server.find(EBasicChangeLog.class, id1);
    assertThat(found).isNotNull();
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.beans).hasSize(1);
    assertThat(readAuditLogger.beans.get(0).getBeanType()).isEqualTo(EBasicChangeLog.class.getName());
    assertThat(readAuditLogger.beans.get(0).getId()).isEqualTo(id1);
  }

  @Test
  public void test_findById_usingL2Cache() {
    resetCounters();

    EBasicChangeLog found = server.find(EBasicChangeLog.class, id1);
    assertThat(found).isNotNull();
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.beans).hasSize(1);
    assertThat(readAuditLogger.beans.get(0).getBeanType()).isEqualTo(EBasicChangeLog.class.getName());
    assertThat(readAuditLogger.beans.get(0).getId()).isEqualTo(id1);

    ServerCache beanCache = server.getServerCacheManager().getBeanCache(EBasicChangeLog.class);
    ServerCacheStatistics statistics = beanCache.getStatistics(false);
    assertThat(statistics.getSize()).isEqualTo(1);
    assertThat(statistics.getHitCount()).isEqualTo(0);

    EBasicChangeLog found2 = server.find(EBasicChangeLog.class, id1);
    assertThat(found2).isNotNull();
    statistics = beanCache.getStatistics(false);
    assertThat(statistics.getSize()).isEqualTo(1);
    assertThat(statistics.getHitCount()).isEqualTo(1);

    assertThat(readAuditLogger.beans).hasSize(2);
  }

  @Test
  public void test_findById_usingL2Cache_sharedBean() {
    resetCounters();

    Country found = server.find(Country.class, "AR");
    assertThat(found).isNotNull();
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.beans).hasSize(1);
    assertThat(readAuditLogger.beans.get(0).getBeanType()).isEqualTo(Country.class.getName());
    assertThat(readAuditLogger.beans.get(0).getId()).isEqualTo("AR");

    ServerCache beanCache = server.getServerCacheManager().getBeanCache(Country.class);
    ServerCacheStatistics statistics = beanCache.getStatistics(false);
    assertThat(statistics.getSize()).isEqualTo(1);
    assertThat(statistics.getHitCount()).isEqualTo(0);

    Country found2 = server.find(Country.class, "AR");
    assertThat(found2).isNotNull();
    statistics = beanCache.getStatistics(false);
    assertThat(statistics.getSize()).isEqualTo(1);
    assertThat(statistics.getHitCount()).isEqualTo(1);

    assertThat(readAuditLogger.beans).hasSize(2);

    Country ref = server.getReference(Country.class, "AR");
    assertThat(readAuditLogger.beans).hasSize(3);
    assertThat(ref).isSameAs(found2);
  }

  @Test
  public void test_findList() {

    resetCounters();

    List<EBasicChangeLog> list = server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findList();

    assertThat(list).hasSize(2);
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(1);
    assertThat(readAuditLogger.many.get(0).getBeanType()).isEqualTo(EBasicChangeLog.class.getName());
    assertThat(readAuditLogger.many.get(0).getIds()).contains(id1, id2);

    server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findList();

    assertThat(readAuditPrepare.count).isEqualTo(2);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(2);
  }

  @Test
  public void test_findList_useL2Cache() {

    resetCounters();

    LoggedSqlCollector.start();

    List<EBasicChangeLog> list = server.find(EBasicChangeLog.class)
        .setUseQueryCache(true)
        .where().startsWith("shortDescription", "readAudit")
        .findList();

    System.out.println("test_findList_useL2Cache> first query");

    assertThat(list).hasSize(2);
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(1);
    assertThat(readAuditLogger.many.get(0).getBeanType()).isEqualTo(EBasicChangeLog.class.getName());
    assertThat(readAuditLogger.many.get(0).getIds()).contains(id1, id2);

    List<EBasicChangeLog> list1 = server.find(EBasicChangeLog.class)
        .setUseQueryCache(true)
        .where().startsWith("shortDescription", "readAudit")
        .findList();

    System.out.println("test_findList_useL2Cache> second query "+list1.size());
    assertThat(list1).hasSize(2);
    List<String> sql = LoggedSqlCollector.stop();
    System.out.println("test_findList_useL2Cache> sql: "+sql);
    //assertThat(sql).hasSize(1);

    System.out.println("test_findList_useL2Cache> prepare:" + readAuditPrepare.count
        + " plans:" + readAuditLogger.plans
        + " many:" + readAuditLogger.many);

    assertThat(readAuditPrepare.count).isEqualTo(2);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(2);
  }

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

    resetCounters();

    FutureList<EBasicChangeLog> futureList = server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findFutureList();

    List<EBasicChangeLog> list = futureList.get();
    assertThat(list).hasSize(2);
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(1);
    assertThat(readAuditLogger.many.get(0).getBeanType()).isEqualTo(EBasicChangeLog.class.getName());
    assertThat(readAuditLogger.many.get(0).getIds()).contains(id1, id2);

    server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findList();

    assertThat(readAuditPrepare.count).isEqualTo(2);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(2);
  }

  @Test
  public void test_findSet() {

    resetCounters();

    Set<EBasicChangeLog> list = server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findSet();

    assertThat(list).hasSize(2);
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(1);
    assertThat(readAuditLogger.many.get(0).getIds()).contains(id1, id2);

    server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findSet();

    assertThat(readAuditPrepare.count).isEqualTo(2);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(2);
  }

  @Test
  public void test_findMap() {

    resetCounters();

    Map<Long,EBasicChangeLog> list = server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findMap();

    assertThat(list).hasSize(2);
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(1);
    assertThat(readAuditLogger.many.get(0).getIds()).contains(id1, id2);

    server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findMap();

    assertThat(readAuditPrepare.count).isEqualTo(2);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(2);
  }

  @Test
  public void test_findEach() {

    resetCounters();

    final AtomicInteger count = new AtomicInteger();
    server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findEach(new QueryEachConsumer<EBasicChangeLog>() {
          @Override
          public void accept(EBasicChangeLog bean) {
            count.incrementAndGet();
          }
        });

    assertThat(count.get()).isEqualTo(2);
    assertThat(readAuditPrepare.count).isEqualTo(1);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(1);
    assertThat(readAuditLogger.many.get(0).getIds()).contains(id1, id2);

    server.find(EBasicChangeLog.class)
        .where().startsWith("shortDescription", "readAudit")
        .findEach(new QueryEachConsumer<EBasicChangeLog>() {
          @Override
          public void accept(EBasicChangeLog bean) {
            count.incrementAndGet();
          }
        });

    assertThat(readAuditPrepare.count).isEqualTo(2);
    assertThat(readAuditLogger.plans).hasSize(1);
    assertThat(readAuditLogger.many).hasSize(2);
  }

  private SpiEbeanServer getServer() {

    System.setProperty("ebean.ignoreExtraDdl", "true");

    ServerConfig config = new ServerConfig();
    config.setName("h2other");
    config.loadFromProperties();

    config.setDdlGenerate(true);
    config.setDdlRun(true);
    config.setDefaultServer(false);
    config.setRegister(false);

    config.addClass(Country.class);
    config.addClass(EBasicChangeLog.class);

    config.setReadAuditLogger(readAuditLogger);
    config.setReadAuditPrepare(readAuditPrepare);

    return (SpiEbeanServer) EbeanServerFactory.create(config);
  }

  private void resetCounters() {
    readAuditLogger.resetCounters();
    readAuditPrepare.resetCounters();
  }

  class TDReadAuditPrepare implements ReadAuditPrepare {

    int count;

    TDReadAuditPrepare(boolean dummy) {
    }
    void resetCounters() {
      count = 0;
    }

    @Override
    public void prepare(ReadEvent event) {
      count++;
      event.setUserId("appUser1");
      event.setUserIpAddress("1.1.1.1");
      event.getUserContext().put("some", "thing");
    }
  }

  class TDReadAuditLogger implements ReadAuditLogger {

    Set<ReadAuditQueryPlan> plans = new HashSet<ReadAuditQueryPlan>();
    List<ReadEvent> beans = new ArrayList<ReadEvent>();
    List<ReadEvent> many = new ArrayList<ReadEvent>();

    TDReadAuditLogger(boolean dummy) {
    }
    void resetCounters() {
      plans.clear();
      beans.clear();
      many.clear();
    }

    @Override
    public void queryPlan(ReadAuditQueryPlan queryPlan) {
      plans.add(queryPlan);
    }

    @Override
    public void auditBean(ReadEvent readBean) {
      beans.add(readBean);
    }

    @Override
    public void auditMany(ReadEvent readMany) {
      many.add(readMany);
    }
  }

}
