package com.avaje.tests.draftable;

import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Query;
import com.avaje.tests.model.draftable.Doc;
import com.avaje.tests.model.draftable.Link;
import org.assertj.core.api.StrictAssertions;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;

import javax.persistence.PersistenceException;
import java.sql.Timestamp;
import java.util.List;

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

public class DocLinkTest extends BaseTestCase {

  @Test
  public void testLazyLoadOnDraftProperty() {

    Link link1 = new Link("something");
    link1.save();

    Ebean.getDefaultServer().publish(Link.class, link1.getId());

    Link link = Ebean.find(Link.class)
        .setId(link1.getId())
        .select("name")
        .findUnique();

    assertThat(link).isNotNull();

    LoggedSqlCollector.start();

    // no lazy loading is invoked as draft property considered @Transient
    assertThat(link.isDraft()).isFalse();

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

  }

  @Test
  public void testUpdate_whenNotPublished() {

    Link link1 = new Link("update");
    StrictAssertions.assertThat(link1.isDraft()).isFalse();

    link1.save();
    StrictAssertions.assertThat(link1.isDraft()).isTrue();

    // perform stateless update
    Link linkUpdate = new Link();
    linkUpdate.setId(link1.getId());
    linkUpdate.setComment("stateless update");
    linkUpdate.setDraft(true);
    linkUpdate.update();

    // invoke lazy loading on the updated bean
    // automatically set asDraft() on lazy loading query
    linkUpdate.getLocation();

    Ebean.deletePermanent(linkUpdate);
  }

  @Test
  public void testDelete_whenNotPublished() {

    Link link1 = new Link("Ld1");
    StrictAssertions.assertThat(link1.isDraft()).isFalse();

    link1.save();
    StrictAssertions.assertThat(link1.isDraft()).isTrue();

    link1.setComment("some change");
    link1.save();

    Ebean.delete(link1);
  }

  @Test
  public void testDeletePermanent_whenPublished2() {

    Link link1 = new Link("Ld2");
    link1.save();
    Ebean.getDefaultServer().publish(Link.class, link1.getId());

    Link link = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
    Ebean.deletePermanent(link);
  }

  @Test
  public void testDeleteLivePermanent_throwsException() {

    Link link1 = new Link("Ld2");
    link1.save();

    Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());

    try {
      Ebean.deletePermanent(live);
      assertTrue("never get here",false);

    } catch (PersistenceException e) {
      // assert nice message when trying to delete live bean
      assertThat(e.getMessage().contains("Explicit Delete is not allowed on a 'live' bean - only draft beans"));
    }
  }

  @Test
  public void testDelete_whenPublished() {

    Link link1 = new Link("Ld2");
    link1.save();
    EbeanServer server = Ebean.getDefaultServer();
    server.publish(Link.class, link1.getId());

    link1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
    StrictAssertions.assertThat(link1.isDraft()).isTrue();

    // this is a soft delete (no automatic publish here, only updates draft)
    link1.delete();

    Link live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
    assertThat(live).isNotNull();
    StrictAssertions.assertThat(live.isDraft()).isFalse();
    StrictAssertions.assertThat(live.isDeleted()).isFalse(); // soft delete state not published yet

    // this is a permanent delete (effectively has automatic publish)
    server.deletePermanent(link1);

    live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
    assertThat(live).isNull();
  }

  @Test
  public void testUpdateLive_throwsException() {

    Link link1 = new Link("forUpdateLive");
    link1.save();

    Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());

    live.setComment("foo");
    // Expect a nice
    try {
      live.save();
      assertTrue("Never get here",false);

    } catch (PersistenceException e) {
      // we want to assert the message is nice and meaningful (and not a optimistic locking exception etc)
      assertThat(e.getMessage()).contains("Save or update is not allowed on a 'live' bean - only draft beans");
    }
  }

  @Test
  public void testDirtyState() {

    Timestamp when = new Timestamp(System.currentTimeMillis());
    String comment = "Really interesting";

    Link link1 = new Link("Ls1");
    link1.setComment(comment);
    link1.setWhenPublish(when);
    link1.save();

    Link draft1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
    StrictAssertions.assertThat(draft1.isDirty()).isTrue();

    EbeanServer server = Ebean.getDefaultServer();

    Link linkLive = server.publish(Link.class, link1.getId(), null);
    StrictAssertions.assertThat(linkLive.getComment()).isEqualTo(comment);
    StrictAssertions.assertThat(linkLive.getWhenPublish()).isEqualTo(when);

    Link draft1b = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
    StrictAssertions.assertThat(draft1b.isDirty()).isFalse();
    StrictAssertions.assertThat(draft1b.getComment()).isNull();
    StrictAssertions.assertThat(draft1b.getWhenPublish()).isNull();

  }

  @Test
  public void testSave() {

    Link link1 = new Link("LinkOne");
    link1.save();

    Link link2 = new Link("LinkTwo");
    link2.save();

    Link link3 = new Link("LinkThree");
    link3.save();

    EbeanServer server = Ebean.getDefaultServer();
    server.publish(Link.class, link1.getId(), null);
    server.publish(Link.class, link2.getId(), null);
    server.publish(Link.class, link3.getId(), null);

    Doc doc1 = new Doc("DocOne");
    doc1.getLinks().add(link1);
    doc1.getLinks().add(link2);
    doc1.save();

    Doc draftDoc1 = server.find(Doc.class)
        .setId(doc1.getId())
        .asDraft()
        .findUnique();

    assertThat(draftDoc1.getLinks()).hasSize(2);

    Doc liveDoc1 = server.publish(Doc.class, doc1.getId(), null);

    assertThat(liveDoc1.getLinks()).hasSize(2);
    assertThat(liveDoc1.getLinks()).extracting("id").contains(link1.getId(), link2.getId());


    draftDoc1.getLinks().remove(0);
    Link remaining = draftDoc1.getLinks().get(0);
    draftDoc1.getLinks().add(link3);

    draftDoc1.save();

    // publish with insert and delete of Links M2M relationship
    Doc liveDoc2 = server.publish(Doc.class, doc1.getId(), null);
    assertThat(liveDoc2.getLinks()).hasSize(2);
    assertThat(liveDoc2.getLinks()).extracting("id").contains(remaining.getId(), link3.getId());

    // delete the draft and live beans (with associated children)
    draftDoc1.delete();
  }


  @Test
  public void testDraftRestore() {

    Link link1 = new Link("Ldr1");
    link1.setLocation("firstLocation");
    link1.save();

    EbeanServer server = Ebean.getDefaultServer();

    Link live = server.publish(Link.class, link1.getId(), null);
    StrictAssertions.assertThat(live.isDraft()).isFalse();

    Link draftLink = Ebean.find(Link.class)
        .setId(link1.getId())
        .asDraft()
        .findUnique();

    draftLink.setLocation("secondLocation");
    draftLink.save();

    server.draftRestore(Link.class, link1.getId(), null);

    draftLink = Ebean.find(Link.class)
        .setId(link1.getId())
        .asDraft()
        .findUnique();

    StrictAssertions.assertThat(draftLink.getLocation()).isEqualTo("firstLocation");

  }

  @Test
  public void testDraftRestoreViaQuery() {

    Link link1 = new Link("Ldr1");
    link1.setLocation("firstLocation");
    link1.setComment("Banana");
    link1.save();

    EbeanServer server = Ebean.getDefaultServer();

    server.publish(Link.class, link1.getId(), null);

    Link draftLink = Ebean.find(Link.class)
        .setId(link1.getId())
        .asDraft()
        .findUnique();

    draftLink.setLocation("secondLocation");
    draftLink.setComment("A good change");
    draftLink.save();

    Query<Link> query = server.find(Link.class).where().eq("id", link1.getId()).query();
    List<Link> links = server.draftRestore(query);

    assertThat(links).hasSize(1);
    StrictAssertions.assertThat(links.get(0).getLocation()).isEqualTo("firstLocation");
    StrictAssertions.assertThat(links.get(0).isDirty()).isEqualTo(false);
    StrictAssertions.assertThat(links.get(0).getComment()).isNull();

  }
}