package danielcortes.xyz.models.efectivo;

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.ArrayList;
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;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class SQLiteEfectivoDAOTest {
  private static final int databaseVersion = 2;

  private static VersionDAO versionDAO;
  private static Faker faker;
  private CajaDAO cajaDAO;
  private EfectivoDAO efectivoDAO;

  @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.cajaDAO = DAOManager.getCajaDAO();
    this.efectivoDAO = DAOManager.getEfectivoDAO();
  }

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

  @ParameterizedTest
  @ValueSource(ints = {1,5,10})
  void getAll(int size) {
    List<Caja> cajas = new ArrayList<>(size);
    for(int x = 0; x < size; x++){
      Caja c = giveRandomCaja();
      cajas.add(c);
      this.cajaDAO.insert(c);
    }

    List<Efectivo> efectivos = new ArrayList<>(size);
    for(Caja c: cajas) {
      Efectivo e = giveRandomEfectivo(c);
      efectivos.add(e);
      this.efectivoDAO.insert(e);
    }

    List<Efectivo> l1 = this.efectivoDAO.getAll();

    assertThat(l1)
        .hasSize(size)
        .containsAll(efectivos);
  }

  @ParameterizedTest
  @ValueSource(ints = {1,5,10})
  void getById(int size) {
    List<Caja> cajas = new ArrayList<>(size);
    for(int x = 0; x < size; x++){
      Caja c = giveRandomCaja();
      cajas.add(c);
      this.cajaDAO.insert(c);
    }

    List<Efectivo> efectivos = new ArrayList<>(size);
    for(Caja c: cajas) {
      Efectivo e = giveRandomEfectivo(c);
      efectivos.add(e);
      this.efectivoDAO.insert(e);
    }

    for(Efectivo e: efectivos){
      Optional<Efectivo> o = this.efectivoDAO.getById(e.getId());
      assertThat(o)
          .isPresent()
          .contains(e);
    }

    Optional<Efectivo> o = this.efectivoDAO.getById(-1);
    assertThat(o)
        .isEmpty();
  }

  @ParameterizedTest
  @ValueSource(ints = {1,5,10})
  void getByCaja(int size) {
    List<Caja> cajas = new ArrayList<>(size);
    for(int x = 0; x < size; x++){
      Caja c = giveRandomCaja();
      cajas.add(c);
      this.cajaDAO.insert(c);
    }

    List<Efectivo> efectivos = new ArrayList<>(size);
    for(Caja c: cajas) {
      Efectivo e = giveRandomEfectivo(c);
      efectivos.add(e);
      this.efectivoDAO.insert(e);
    }

    for(Efectivo e: efectivos) {
      Optional<Efectivo> o = this.efectivoDAO.getByCaja(e.getCaja());
      assertThat(o)
          .isPresent()
          .contains(e);
    }

    Optional<Efectivo> o = this.efectivoDAO.getByCaja(Caja.EMPTY);
    assertThat(o)
        .isEmpty();
  }

  @ParameterizedTest
  @ValueSource(ints = {1,5,10})
  void getTotalEfectivo(int size) {
    List<Caja> cajas = new ArrayList<>(size);
    for(int x = 0; x < size; x++){
      Caja c = giveRandomCaja();
      cajas.add(c);
      this.cajaDAO.insert(c);
    }

    for(Caja c: cajas) {
      Efectivo e = giveRandomEfectivo(c);
      this.efectivoDAO.insert(e);
    }

    for(Caja c: cajas) {
      Optional<Efectivo> o = this.efectivoDAO.getByCaja(c);
      int sum = this.efectivoDAO.getTotalEfectivo(c);

      assertThat(sum)
          .isEqualTo(
              o.get().getDiez() +
              o.get().getCincuenta() +
              o.get().getCien() +
              o.get().getQuinientos() +
              o.get().getMil() +
              o.get().getDosMil() +
              o.get().getCincoMil() +
              o.get().getDiezMil() +
              o.get().getVeinteMil()
          );
    }

    assertThat(this.efectivoDAO.getTotalEfectivo(Caja.EMPTY))
        .isEqualTo(0);
  }

  @ParameterizedTest
  @ValueSource(ints = {1, 5, 10})
  void insert(int size) {
    Caja c = giveRandomCaja();
    this.cajaDAO.insert(c);

    Efectivo e = giveRandomEfectivo(c);
    assertThat(e.getId())
        .isEqualTo(0);
    this.efectivoDAO.insert(e);
    assertThat(e.getId())
        .isNotEqualTo(0);
  }

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

    Efectivo f1 = giveRandomEfectivo(c1);
    this.efectivoDAO.insert(f1);

    Optional<Efectivo> o1 = this.efectivoDAO.getById(f1.getId());

    Efectivo f2 = updateRandomEfectivo(f1);

    assertThat(o1)
        .isPresent()
        .contains(f1);

    assertThat(f1).isNotEqualTo(f2);

    this.efectivoDAO.update(f2);

    Optional<Efectivo> o2 = this.efectivoDAO.getById(f2.getId());

    assertThat(o2)
        .isPresent()
        .contains(f2);
  }

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

    Efectivo e = giveRandomEfectivo(c);
    this.efectivoDAO.insert(e);

    Optional<Efectivo> o1 = this.efectivoDAO.getById(e.getId());
    assertThat(o1)
        .isPresent()
        .contains(e);

    this.efectivoDAO.delete(e);

    Optional<Efectivo> o2 = this.efectivoDAO.getById(e.getId());
    assertThat(o2)
        .isEmpty();
  }

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

  Efectivo giveRandomEfectivo(Caja caja){
    Efectivo e = new Efectivo();
    e.setDiez(faker.number().numberBetween(0, 10000000));
    e.setCincuenta(faker.number().numberBetween(0, 10000000));
    e.setCien(faker.number().numberBetween(0, 10000000));
    e.setQuinientos(faker.number().numberBetween(0, 10000000));
    e.setMil(faker.number().numberBetween(0, 10000000));
    e.setDosMil(faker.number().numberBetween(0, 10000000));
    e.setCincoMil(faker.number().numberBetween(0, 10000000));
    e.setDiezMil(faker.number().numberBetween(0, 10000000));
    e.setVeinteMil(faker.number().numberBetween(0, 10000000));
    e.setCaja(caja);
    return e;
  }

  Efectivo updateRandomEfectivo(Efectivo efectivo){
    Efectivo e = giveRandomEfectivo(efectivo.getCaja());
    e.setId(efectivo.getId());
    return e;
  }
}