package danielcortes.xyz.models.calculo_fondo;

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

import com.github.javafaker.Faker;
import danielcortes.xyz.data.ConnectionHolder;
import danielcortes.xyz.data.DAOManager;
import danielcortes.xyz.data.SQLiteConnectionHolder;
import danielcortes.xyz.models.caja.Caja;
import danielcortes.xyz.models.caja.CajaDAO;
import danielcortes.xyz.models.version.SQLiteVersionDAO;
import danielcortes.xyz.models.version.VersionDAO;
import danielcortes.xyz.utils.RandomLocalDate;
import java.io.File;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class SQLiteCalculoFondoDAOTest {
  private static Faker faker;
  private static final int databaseVersion = 2;
  private static VersionDAO versionDAO;
  private CalculoFondoDAO calculoFondoDAO;
  private CajaDAO cajaDAO;

  @BeforeAll
  static void setAll() {
    ConnectionHolder connectionHolder = new SQLiteConnectionHolder("jdbc:sqlite:/tmp/tmp.sqlite");
    DAOManager.setup(connectionHolder);
    versionDAO = new SQLiteVersionDAO(connectionHolder);
    faker = new Faker();
  }

  @BeforeEach
  void setUp() {
    versionDAO.updateTo(databaseVersion);
    this.calculoFondoDAO = DAOManager.getCalculoFondoDAO();
    this.cajaDAO = DAOManager.getCajaDAO();
  }

  @AfterEach
  void tearDown() {
    //noinspection ResultOfMethodCallIgnored
    new File("/tmp/tmp.sqlite").delete();
  }

  @Test
  void getAll() {
    Caja c = giveRandomCaja();
    this.cajaDAO.insert(c);

    CalculoFondo f1 = giveRandomCalculoFondo(c);
    CalculoFondo f2 = giveRandomCalculoFondo(c);

    this.calculoFondoDAO.insert(f1);
    this.calculoFondoDAO.insert(f2);

    List<CalculoFondo> list = this.calculoFondoDAO.getAll();

    assertThat(list)
        .hasSize(2)
        .contains(f1)
        .contains(f2);
  }

  @Test
  void getByCaja() {
    Caja c1 = giveRandomCaja();
    Caja c2 = giveRandomCaja();

    this.cajaDAO.insert(c1);
    this.cajaDAO.insert(c2);

    CalculoFondo f1 = giveRandomCalculoFondo(c1);
    CalculoFondo f2 = giveRandomCalculoFondo(c1);

    CalculoFondo f3 = giveRandomCalculoFondo(c2);
    CalculoFondo f4 = giveRandomCalculoFondo(c2);
    CalculoFondo f5 = giveRandomCalculoFondo(c2);

    this.calculoFondoDAO.insert(f1);
    this.calculoFondoDAO.insert(f2);
    this.calculoFondoDAO.insert(f3);
    this.calculoFondoDAO.insert(f4);
    this.calculoFondoDAO.insert(f5);

    List<CalculoFondo> l1 = this.calculoFondoDAO.getByCaja(c1);
    List<CalculoFondo> l2 = this.calculoFondoDAO.getByCaja(c2);
    List<CalculoFondo> l3 = this.calculoFondoDAO.getByCaja(Caja.EMPTY);

    assertThat(l1)
        .hasSize(2)
        .containsExactlyInAnyOrder(f1, f2)
        .doesNotContain(f3, f4, f5);

    assertThat(l2)
        .hasSize(3)
        .containsExactlyInAnyOrder(f3, f4, f5)
        .doesNotContain(f1, f2);

    assertThat(l3)
        .hasSize(0);
  }

  @Test
  void getById() {
    Caja c1 = giveRandomCaja();
    this.cajaDAO.insert(c1);

    CalculoFondo f1 = giveRandomCalculoFondo(c1);
    CalculoFondo f2 = giveRandomCalculoFondo(c1);

    this.calculoFondoDAO.insert(f1);
    this.calculoFondoDAO.insert(f2);

    Optional<CalculoFondo> o1 = this.calculoFondoDAO.getById(f1.getId());
    Optional<CalculoFondo> o2 = this.calculoFondoDAO.getById(f2.getId());
    Optional<CalculoFondo> o3 = this.calculoFondoDAO.getById(-1);

    assertThat(o1)
        .isPresent()
        .contains(f1);
    assertThat(o2)
        .isPresent()
        .contains(f2);
    assertThat(o3)
        .isEmpty();
  }

  @Test
  void getTotalCalculoFondo() {
    Caja c1 = giveRandomCaja();
    Caja c2 = giveRandomCaja();

    this.cajaDAO.insert(c1);
    this.cajaDAO.insert(c2);

    CalculoFondo f1 = giveRandomCalculoFondo(c1);
    CalculoFondo f2 = giveRandomCalculoFondo(c1);
    CalculoFondo f3 = giveRandomCalculoFondo(c2);
    CalculoFondo f4 = giveRandomCalculoFondo(c2);
    CalculoFondo f5 = giveRandomCalculoFondo(c2);

    this.calculoFondoDAO.insert(f1);
    this.calculoFondoDAO.insert(f2);
    this.calculoFondoDAO.insert(f3);
    this.calculoFondoDAO.insert(f4);
    this.calculoFondoDAO.insert(f5);

    int t1 = this.calculoFondoDAO.getTotalCalculoFondo(c1);
    int t2 = this.calculoFondoDAO.getTotalCalculoFondo(c2);

    assertThat(t1)
        .isEqualTo(f1.getValor() + f2.getValor());
    assertThat(t2)
        .isEqualTo(f3.getValor() + f4.getValor() + f5.getValor());
  }

  @Test
  void insert() {
    Caja c1 = giveRandomCaja();
    this.cajaDAO.insert(c1);

    CalculoFondo f1 = giveRandomCalculoFondo(c1);
    CalculoFondo f2 = giveRandomCalculoFondo(c1);

    assertThat(0)
        .isEqualTo(f1.getId())
        .isEqualTo(f2.getId());

    this.calculoFondoDAO.insert(f1);
    this.calculoFondoDAO.insert(f2);

    assertThat(0)
        .isNotEqualTo(f1.getId())
        .isNotEqualTo(f2.getId());

    List<CalculoFondo> l = this.calculoFondoDAO.getAll();

    assertThat(l)
        .hasSize(2)
        .containsExactlyInAnyOrder(f1, f2);
  }

  @Test
  void update() {
    Caja c1 = giveRandomCaja();
    Caja c2 = giveRandomCaja();
    this.cajaDAO.insert(c1);
    this.cajaDAO.insert(c2);

    CalculoFondo f1 = giveRandomCalculoFondo(c1);
    CalculoFondo f2 = giveRandomCalculoFondo(c1);

    this.calculoFondoDAO.insert(f1);
    this.calculoFondoDAO.insert(f2);

    f1 = giveRandomCalculoFondo(c2, f1.getId());
    f2 = giveRandomCalculoFondo(c2, f2.getId());

    this.calculoFondoDAO.update(f1);
    this.calculoFondoDAO.update(f2);

    List<CalculoFondo> l = this.calculoFondoDAO.getAll();

    assertThat(l)
        .hasSize(2)
        .containsExactlyInAnyOrder(f1, f2);
  }

  @Test
  void delete() {
    Caja c1 = giveRandomCaja();
    this.cajaDAO.insert(c1);

    CalculoFondo f1 = giveRandomCalculoFondo(c1);
    CalculoFondo f2 = giveRandomCalculoFondo(c1);
    this.calculoFondoDAO.insert(f1);
    this.calculoFondoDAO.insert(f2);

    List<CalculoFondo> l1 = this.calculoFondoDAO.getAll();

    assertThat(l1)
        .hasSize(2);

    this.calculoFondoDAO.delete(f1);
    this.calculoFondoDAO.delete(f2);

    List<CalculoFondo> l2 = this.calculoFondoDAO.getAll();

    assertThat(l2)
        .hasSize(0);
  }

  @Test
  void shouldNotInsertWithCajaEMPTY(){
    Caja c = Caja.EMPTY;

    CalculoFondo f = giveRandomCalculoFondo(c);
    this.calculoFondoDAO.insert(f);

    List<CalculoFondo> l = this.calculoFondoDAO.getAll();

    assertThat(l).hasSize(0);
  }

  @Test
  void shouldNotUpdateWithCajaEMPTY(){
    Caja c = giveRandomCaja();
    this.cajaDAO.insert(c);

    CalculoFondo f1 = giveRandomCalculoFondo(c);
    this.calculoFondoDAO.insert(f1);

    CalculoFondo f2 = giveRandomCalculoFondo(Caja.EMPTY, f1.getId());
    this.calculoFondoDAO.update(f2);

    Optional<CalculoFondo> o = this.calculoFondoDAO.getById(f1.getId());

    assertThat(o)
        .isPresent()
        .contains(f1);
  }

  Caja giveRandomCaja() {
    Caja caja = new Caja();
    caja.setFondo(faker.number().numberBetween(0, 10000000));
    caja.setFecha(RandomLocalDate.random());
    return caja;
  }

  CalculoFondo giveRandomCalculoFondo(Caja caja) {
    CalculoFondo calculoFondo = new CalculoFondo();
    calculoFondo.setCaja(caja);
    calculoFondo.setDescripcion(faker.lorem().characters());
    calculoFondo.setValor(faker.number().numberBetween(0, 10000000));
    return calculoFondo;
  }

  CalculoFondo giveRandomCalculoFondo(Caja caja, int id) {
    CalculoFondo calculoFondo = giveRandomCalculoFondo(caja);
    calculoFondo.setId(id);
    return calculoFondo;
  }
}