// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab

package org.mariadb.jdbc.integration.resultset;

import static org.junit.jupiter.api.Assertions.*;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mariadb.jdbc.Common;
import org.mariadb.jdbc.Statement;

public class StreamingRowChangeTest extends Common {

  private final String NOT_FORWARD = "Operation not permit on TYPE_FORWARD_ONLY resultSet";
  private final Class<? extends java.lang.Exception> sqle = SQLException.class;

  @AfterAll
  public static void after2() throws SQLException {
    sharedConn.createStatement().execute("DROP TABLE ResultSetTest");
  }

  @BeforeAll
  public static void beforeAll2() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    stmt.execute("DROP TABLE IF EXISTS ResultSetTest");
    stmt.execute("CREATE TABLE ResultSetTest (t1 int not null primary key auto_increment, t2 int)");
    stmt.execute("INSERT INTO ResultSetTest(t2) values (1),(2),(3),(4),(5),(6),(7),(8)");
  }

  @Test
  public void closedRes() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    rs.close();
    String NOT_SUPPORTED = "Operation not permit on a closed resultSet";
    assertThrowsContains(sqle, rs::next, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::isBeforeFirst, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::isAfterLast, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::isFirst, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::isLast, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::beforeFirst, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::afterLast, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::first, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::last, NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::getRow, NOT_SUPPORTED);
    assertThrowsContains(sqle, () -> rs.absolute(0), NOT_SUPPORTED);
    assertThrowsContains(sqle, () -> rs.relative(1), NOT_SUPPORTED);
    assertThrowsContains(sqle, rs::previous, NOT_SUPPORTED);
  }

  @Test
  public void next() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    for (int i = 1; i < 9; i++) {
      assertTrue(rs.next());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.next());

    // next fetching no result
    stmt.setFetchSize(8);
    rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    for (int i = 1; i < 9; i++) {
      assertTrue(rs.next());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.next());

    // next fetching no result keeping all results
    stmt = sharedConn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
    stmt.setFetchSize(8);
    rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    for (int i = 1; i < 9; i++) {
      assertTrue(rs.next());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.next());
  }

  @Test
  public void isAfterLast() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    isAfterLast(stmt);
    stmt.setFetchSize(3);
    isAfterLast(stmt);
    stmt.setFetchSize(4);
    isAfterLast(stmt);
  }

  public void isAfterLast(Statement stmt) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    for (int i = 1; i < 9; i++) {
      assertTrue(rs.next());
      assertFalse(rs.isAfterLast());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.isAfterLast());
    assertFalse(rs.next());
    assertTrue(rs.isAfterLast());

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest WHERE t1 < 0");
    assertFalse(rs.isAfterLast());
    assertFalse(rs.next());
    assertFalse(rs.isAfterLast());
  }

  @Test
  public void isFirst() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    Statement stmt2 =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
    isFirst(stmt, stmt2);

    stmt.setFetchSize(3);
    stmt2.setFetchSize(3);
    isFirst(stmt, stmt2);

    stmt.setFetchSize(4);
    stmt2.setFetchSize(4);
    isFirst(stmt, stmt2);
  }

  private void isFirst(Statement stmt, Statement stmt2) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertFalse(rs.isFirst());
    assertTrue(rs.next());
    assertTrue(rs.isFirst());
    while (rs.next()) {
      assertFalse(rs.isFirst());
    }
    assertFalse(rs.isFirst());

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest Where 1 = -1");
    assertFalse(rs.isFirst());
    assertFalse(rs.next());
    assertFalse(rs.isFirst());

    rs = stmt2.executeQuery("SELECT * FROM ResultSetTest");
    assertFalse(rs.isFirst());
    assertTrue(rs.next());
    assertTrue(rs.isFirst());
    while (rs.next()) {
      assertFalse(rs.isFirst());
    }
    assertFalse(rs.isFirst());

    rs = stmt2.executeQuery("SELECT * FROM ResultSetTest Where 1 = -1");
    assertFalse(rs.isFirst());
    assertFalse(rs.next());
    assertFalse(rs.isFirst());
  }

  @Test
  public void isLast() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    isLast(stmt);
    stmt.setFetchSize(3);
    isLast(stmt);
    stmt.setFetchSize(4);
    isLast(stmt);
  }

  private void isLast(Statement stmt) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertFalse(rs.isLast());
    for (int i = 1; i < 8; i++) {
      assertTrue(rs.next());
      assertFalse(rs.isLast());
      assertEquals(i, rs.getInt(1));
    }
    assertTrue(rs.next());
    assertEquals(8, rs.getInt(1));
    assertTrue(rs.isLast());
    assertFalse(rs.next());
    assertFalse(rs.isLast());

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest Where 1 = -1");
    rs.isLast();
    assertFalse(rs.isLast());
    assertFalse(rs.next());
    assertFalse(rs.isLast());

    stmt = sharedConn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    stmt.setFetchSize(3);
    rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertFalse(rs.isLast());
    for (int i = 1; i < 8; i++) {
      assertTrue(rs.next());
      assertFalse(rs.isLast());
      assertEquals(i, rs.getInt(1));
    }
    assertTrue(rs.next());
    assertEquals(8, rs.getInt(1));
    assertTrue(rs.isLast());
    assertFalse(rs.next());
    assertFalse(rs.isLast());

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest Where 1 = -1");
    assertFalse(rs.isLast());
    assertFalse(rs.next());
    assertFalse(rs.isLast());
  }

  @Test
  public void isBeforeFirst() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    isBeforeFirst(stmt);
    stmt.setFetchSize(3);
    isBeforeFirst(stmt);
  }

  private void isBeforeFirst(Statement stmt) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertTrue(rs.isBeforeFirst());
    while (rs.next()) {
      assertFalse(rs.isBeforeFirst());
    }
    assertFalse(rs.isBeforeFirst());

    stmt = sharedConn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    stmt.setFetchSize(3);
    rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertTrue(rs.isBeforeFirst());
    while (rs.next()) {
      assertFalse(rs.isBeforeFirst());
    }
    assertFalse(rs.isBeforeFirst());
  }

  @Test
  public void beforeFirst() throws SQLException {
    Statement stmt =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    beforeFirst(stmt);
    stmt.setFetchSize(3);
    beforeFirst(stmt);
  }

  private void beforeFirst(Statement stmt) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    for (int i = 1; i < 9; i++) {
      assertTrue(rs.next());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.next());
    assertFalse(rs.isBeforeFirst());
    rs.beforeFirst();
    assertTrue(rs.isBeforeFirst());
    assertTrue(rs.next());
    assertEquals(1, rs.getInt(1));

    stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs2 = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertThrowsContains(sqle, rs2::beforeFirst, NOT_FORWARD);
  }

  @Test
  public void afterLast() throws SQLException {
    Statement stmt =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    afterLast(stmt);
    stmt.setFetchSize(3);
    afterLast(stmt);
  }

  private void afterLast(Statement stmt) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    rs.afterLast();
    assertFalse(rs.next());
    assertTrue(rs.previous());
    assertEquals(8, rs.getInt(1));

    stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs2 = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertThrowsContains(sqle, rs2::afterLast, NOT_FORWARD);
  }

  @Test
  public void first() throws SQLException {
    Statement stmt =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    first(stmt);
    stmt.setFetchSize(3);
    first(stmt);
  }

  private void first(Statement stmt) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    rs.afterLast();
    assertTrue(rs.first());
    assertEquals(1, rs.getInt(1));

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest WHERE t1 = -1");
    assertFalse(rs.first());

    stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs2 = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertThrowsContains(sqle, rs2::first, NOT_FORWARD);
  }

  @Test
  public void last() throws SQLException {
    Statement stmt =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    last(stmt);
    stmt.setFetchSize(3);
    last(stmt);
  }

  private void last(Statement stmt) throws SQLException {
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertTrue(rs.last());
    assertEquals(8, rs.getInt(1));

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest WHERE t1 = -1");
    assertFalse(rs.last());

    stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertTrue(rs.last());
    assertEquals(8, rs.getInt(1));

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest WHERE t1 = -1");
    assertFalse(rs.last());
  }

  @Test
  public void getRow() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertEquals(0, rs.getRow());
    for (int i = 1; i < 9; i++) {
      assertTrue(rs.next());
      assertEquals(0, rs.getRow());
    }
    assertFalse(rs.next());
    assertEquals(0, rs.getRow());

    rs = stmt.executeQuery("SELECT * FROM ResultSetTest WHERE t1 = -1");
    assertEquals(0, rs.getRow());
    assertFalse(rs.next());
    assertEquals(0, rs.getRow());
  }

  @Test
  public void absolute() throws SQLException {
    Statement stmt =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    stmt.setFetchSize(3);
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertFalse(rs.absolute(0));
    assertTrue(rs.absolute(2));
    assertEquals(2, rs.getRow());
    assertEquals(2, rs.getInt(1));
    assertTrue(rs.absolute(6));
    assertEquals(6, rs.getRow());
    assertEquals(6, rs.getInt(1));
    assertFalse(rs.absolute(9));
    assertTrue(rs.absolute(4));
    assertEquals(4, rs.getRow());
    assertEquals(4, rs.getInt(1));
    assertTrue(rs.absolute(5));
    assertEquals(5, rs.getRow());
    assertEquals(5, rs.getInt(1));
    assertFalse(rs.absolute(9));
    assertTrue(rs.isAfterLast());
    assertTrue(rs.absolute(1));
    assertTrue(rs.isFirst());

    assertFalse(rs.absolute(-9));
    assertFalse(rs.isAfterLast());
    assertTrue(rs.absolute(-8));
    assertTrue(rs.isFirst());
    assertTrue(rs.absolute(-3));
    assertEquals(6, rs.getInt(1));

    stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs2 = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertThrowsContains(sqle, () -> rs2.absolute(0), NOT_FORWARD);
  }

  @Test
  public void relative() throws SQLException {
    Statement stmt =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    stmt.setFetchSize(3);
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    rs.relative(2);
    assertEquals(2, rs.getInt(1));
    rs.relative(2);
    assertEquals(4, rs.getInt(1));
    rs.relative(10);
    assertTrue(rs.isAfterLast());
    rs.relative(-20);
    assertTrue(rs.isBeforeFirst());
    rs.relative(5);
    assertEquals(5, rs.getInt(1));

    stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs2 = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertThrowsContains(sqle, () -> rs2.relative(0), NOT_FORWARD);
  }

  @Test
  public void previous() throws SQLException {
    Statement stmt =
        sharedConn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    stmt.setFetchSize(3);
    ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertFalse(rs.previous());
    rs.afterLast();
    for (int i = 8; i > 0; i--) {
      assertTrue(rs.previous());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.previous());

    stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs2 = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertThrowsContains(sqle, rs2::previous, NOT_FORWARD);
  }

  @Test
  public void getFetchSize() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertEquals(3, rs.getFetchSize());

    assertTrue(rs.next());
    assertEquals(1, rs.getInt(1));
    assertTrue(rs.next());
    assertEquals(2, rs.getInt(1));

    rs.setFetchSize(2);
    assertEquals(2, rs.getFetchSize());

    for (int i = 3; i < 9; i++) {
      assertTrue(rs.next());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.next());
    assertThrowsContains(sqle, () -> rs.setFetchSize(-2), "invalid fetch size -2");
  }

  @Test
  public void removeStreaming() throws SQLException {
    Statement stmt = sharedConn.createStatement();
    stmt.setFetchSize(3);
    final ResultSet rs = stmt.executeQuery("SELECT * FROM ResultSetTest");
    assertEquals(3, rs.getFetchSize());
    rs.setFetchSize(0);
    assertEquals(0, rs.getFetchSize());

    for (int i = 1; i < 9; i++) {
      assertTrue(rs.next());
      assertEquals(i, rs.getInt(1));
    }
    assertFalse(rs.next());
  }
}
