package library.service;

import java.util.List;
import java.util.Optional;

import library.dao.BookDao;
import library.domain.Book;
import library.dto.NewBook;
import library.errors.NotFoundException;
import library.errors.ValidationException;

/**
 * Business rules for books.
 *
 * Speaks in domain terms (Book, exceptions), never HTTP status codes. Failures
 * are thrown as our own exceptions; ErrorHandler decides the status code.
 * Naming convention: find = "maybe" (returns Optional or a list), get = "must
 * exist" (returns the thing or throws NotFoundException).
 */
public class BookService {

	private final BookDao dao;

	public BookService(BookDao dao) {
		this.dao = dao;
	}

	// GET /books
	public List<Book> findAll() {
		return dao.findAll();
	}

	// GET /books?author=...
	public List<Book> findByAuthor(String author) {
		return dao.findByAuthor(author);
	}

	// GET /books/{id}: the book, or NotFoundException if there is none
	public Book get(int id) {
		Optional<Book> book = dao.findById(id);
		if (book.isEmpty()) {
			throw new NotFoundException("No book with id " + id);
		}
		return book.get();
	}

	// POST /books. A duplicate ISBN comes back from BookDao as a ConflictException;
	// a missing ISBN or author is caught by the NOT NULL constraint (error 1048 →
	// 400).
	public Book create(NewBook nb) {
		validate(nb);
		Book book = new Book(null, nb.isbn(), nb.title(), nb.author(), nb.publishedOn(), nb.replacementCost(), true);
		int id = dao.insert(book);
		// Re-read: the stored row includes the default for available
		Optional<Book> created = dao.findById(id);
		if (created.isEmpty()) {
			// Can't happen unless something is badly wrong: our bug, not the client's (→
			// 500)
			throw new IllegalStateException("Book " + id + " was inserted but can't be found");
		}
		return created.get();
	}

	// PUT /books/{id}: replaces the book's details (not its availability)
	public Book update(int id, NewBook nb) {
		validate(nb);
		Book book = new Book(id, nb.isbn(), nb.title(), nb.author(), nb.publishedOn(), nb.replacementCost(), true); // available
																													// is
																													// ignored
																													// by
																													// update
		if (!dao.update(book)) {
			throw new NotFoundException("No book with id " + id);
		}
		return get(id);
	}

	// DELETE /books/{id}. A book with loan history comes back from BookDao as a
	// ConflictException.
	public void delete(int id) {
		if (!dao.delete(id)) {
			throw new NotFoundException("No book with id " + id);
		}
	}

	private static void validate(NewBook nb) {
		if (nb == null) {
			throw new ValidationException("request body is required"); // the body was JSON null
		}
		if (nb.title() == null || nb.title().isBlank()) {
			throw new ValidationException("title is required");
		}
	}
}
