package org.integration;

import io.ebean.redis.DuelCache;
import org.domain.Person;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.config.DatabaseConfig;
import org.domain.query.QPerson;
import org.junit.jupiter.api.Test;

import javax.sql.DataSource;

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

public class ClusterTest {

  private Database createOther(DataSource dataSource) {
    DatabaseConfig config = new DatabaseConfig();
    config.setDataSource(dataSource);
    config.loadFromProperties();
    config.setDefaultServer(false);
    config.setName("other");
    config.setDdlGenerate(false);
    config.setDdlRun(false);
    return DatabaseFactory.create(config);
  }

  @Test
  public void testBothNear() throws InterruptedException {
    // ensure the default server exists first
    final Database db = DB.getDefault();
    Database other = createOther(db.pluginApi().dataSource());

    new QPerson()
      .name.eq("Someone")
      .delete();

    Person foo = new Person("Someone");
    foo.save();

    DB.cacheManager().clearAll();
    db.metaInfo().resetAllMetrics();
    other.metaInfo().resetAllMetrics();

    Person fooA = DB.find(Person.class, foo.getId());
    allowAsyncMessaging(); // allow time for background cache load
    Person fooB = other.find(Person.class, foo.getId());

    DuelCache dualCacheA = db.cacheManager().beanCache(Person.class).unwrap(DuelCache.class);
    assertCounts(dualCacheA, 0, 1, 0, 1);
    fooA = DB.find(Person.class, foo.getId());
    assertCounts(dualCacheA, 1, 1, 0, 1);
    fooB = other.find(Person.class, foo.getId());
    fooA = DB.find(Person.class, foo.getId());
    assertCounts(dualCacheA, 2, 1, 0, 1);
    fooB = other.find(Person.class, foo.getId());
    DuelCache dualCacheB = other.cacheManager().beanCache(Person.class).unwrap(DuelCache.class);
    assertCounts(dualCacheB, 2, 1, 1, 0);
  }

  @Test
  public void test() throws InterruptedException {
    // ensure the default server exists first
    final Database db = DB.getDefault();
    Database other = createOther(db.pluginApi().dataSource());

    for (int i = 0; i < 10; i++) {
      Person foo = new Person("name " + i);
      foo.save();
    }

    other.cacheManager().clearAll();
    other.metaInfo().resetAllMetrics();

    DuelCache dualCache = other.cacheManager().beanCache(Person.class).unwrap(DuelCache.class);

    Person foo0 = other.find(Person.class, 1);
    assertCounts(dualCache, 0, 1, 0, 1);

    other.find(Person.class, 1);
    assertCounts(dualCache, 1, 1, 0, 1);

    other.find(Person.class, 1);
    assertCounts(dualCache, 2, 1, 0, 1);

    other.find(Person.class, 1);
    assertCounts(dualCache, 3, 1, 0, 1);

    other.find(Person.class, 2);
    assertCounts(dualCache, 3, 2, 0, 2);

    foo0.setName("name2");
    foo0.save();
    allowAsyncMessaging();

    Person foo3 = other.find(Person.class, 1);
    assertThat(foo3.getName()).isEqualTo("name2");
    assertCounts(dualCache, 3, 3, 1, 2);

    foo0.setName("name3");
    foo0.save();
    allowAsyncMessaging();

    foo3 = other.find(Person.class, 1);
    assertThat(foo3.getName()).isEqualTo("name3");
    assertCounts(dualCache, 3, 4, 2, 2);
  }

  private void assertCounts(DuelCache dualCache, int nearHits, int nearMiss, int remoteHit, int remoteMiss) {
    assertThat(dualCache.getNearHitCount()).isEqualTo(nearHits);
    assertThat(dualCache.getNearMissCount()).isEqualTo(nearMiss);
    assertThat(dualCache.getRemoteHitCount()).isEqualTo(remoteHit);
    assertThat(dualCache.getRemoteMissCount()).isEqualTo(remoteMiss);
  }

  private void allowAsyncMessaging() throws InterruptedException {
    Thread.sleep(100);
  }
}
