package library.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import library.db.DB;
import library.domain.Book;

/**
 * All SQL for BOOKS.
 */
public class BookDao {

	private static final String COLUMNS = "book_id, isbn, title, author, published_on, replacement_cost, available";

	// GET /books
	public List<Book> findAll() throws SQLException {
		String sql = "SELECT " + COLUMNS + " FROM BOOKS ORDER BY title";
		List<Book> books = new ArrayList<>();
		try (Connection conn = DB.getConnection();
				PreparedStatement ps = conn.prepareStatement(sql);
				ResultSet rs = ps.executeQuery()) {
			while (rs.next()) {
				books.add(map(rs));
			}
		}
		return books;
	}

	// GET /books?author=...
	public List<Book> findByAuthor(String author) throws SQLException {
		String sql = "SELECT " + COLUMNS + " FROM BOOKS WHERE author = ? ORDER BY title";
		List<Book> books = new ArrayList<>();
		try (Connection conn = DB.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
			ps.setString(1, author);
			try (ResultSet rs = ps.executeQuery()) {
				while (rs.next()) {
					books.add(map(rs));
				}
			}
		}
		return books;
	}

	// GET /books/{id}
	public Optional<Book> findById(int bookId) throws SQLException {
		String sql = "SELECT " + COLUMNS + " FROM BOOKS WHERE book_id = ?";
		try (Connection conn = DB.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
			ps.setInt(1, bookId);
			try (ResultSet rs = ps.executeQuery()) {
				return rs.next() ? Optional.of(map(rs)) : Optional.empty();
			}
		}
	}

	// POST /books → returns the new book_id.
	// available is left out of the INSERT so the column default (TRUE) applies.
	public int insert(Book b) throws SQLException {
		String sql = "INSERT INTO BOOKS (isbn, title, author, published_on, replacement_cost) "
				+ "VALUES (?, ?, ?, ?, ?)";
		try (Connection conn = DB.getConnection();
				PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
			ps.setString(1, b.isbn());
			ps.setString(2, b.title());
			ps.setString(3, b.author());
			ps.setObject(4, b.publishedOn()); // null → SQL NULL
			ps.setBigDecimal(5, b.replacementCost()); // null → SQL NULL
			ps.executeUpdate();
			try (ResultSet keys = ps.getGeneratedKeys()) {
				keys.next();
				return keys.getInt(1);
			}
		}
	}

	// PUT /books/{id} → false if no such book.
	// Leaves available alone: only checkout and return should change it.
	public boolean update(Book b) throws SQLException {
		String sql = "UPDATE BOOKS SET isbn = ?, title = ?, author = ?, published_on = ?, "
				+ "replacement_cost = ? WHERE book_id = ?";
		try (Connection conn = DB.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
			ps.setString(1, b.isbn());
			ps.setString(2, b.title());
			ps.setString(3, b.author());
			ps.setObject(4, b.publishedOn());
			ps.setBigDecimal(5, b.replacementCost());
			ps.setInt(6, b.bookId());
			return ps.executeUpdate() == 1;
		}
	}

	// Part of checkout and return → false if no such book
	public boolean setAvailable(int bookId, boolean available) throws SQLException {
		String sql = "UPDATE BOOKS SET available = ? WHERE book_id = ?";
		try (Connection conn = DB.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
			ps.setBoolean(1, available);
			ps.setInt(2, bookId);
			return ps.executeUpdate() == 1;
		}
	}

	// DELETE /books/{id} → false if no such book.
	// A book with loan history fails with SQLException 1451 (LOANS.book_id is ON
	// DELETE RESTRICT).
	public boolean delete(int bookId) throws SQLException {
		String sql = "DELETE FROM BOOKS WHERE book_id = ?";
		try (Connection conn = DB.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
			ps.setInt(1, bookId);
			return ps.executeUpdate() == 1;
		}
	}

	private static Book map(ResultSet rs) throws SQLException {
		return new Book(rs.getInt("book_id"), // NOT NULL: a primitive getter is safe
				rs.getString("isbn"), rs.getString("title"), rs.getString("author"),
				rs.getObject("published_on", LocalDate.class), // null for SQL NULL
				rs.getBigDecimal("replacement_cost"), // null for SQL NULL
				rs.getBoolean("available")); // NOT NULL
	}
}
