package library.service;

import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;

import library.dao.BookDao;
import library.dao.LoanDao;
import library.domain.Book;
import library.domain.Loan;
import library.domain.Member;
import library.domain.Roles;

/**
 * Checking out, returning, and viewing loans.
 *
 * Every method takes the current member, which comes from the session
 * (AuthFilter), never from the request. Librarians see every loan; members see
 * only their own, enforced in the SQL WHERE clause.
 */
public class LoanService {

	private static final int LOAN_DAYS = 14;

	private final LoanDao loanDao;
	private final BookDao bookDao;

	public LoanService(LoanDao loanDao, BookDao bookDao) {
		this.loanDao = loanDao;
		this.bookDao = bookDao;
	}

	// GET /loans
	public List<Loan> listLoans(Member current) throws SQLException {
		if (current.role().equals(Roles.LIBRARIAN)) {
			return loanDao.findAll();
		}
		return loanDao.findByMember(current.memberId());
	}

	// GET /loans/{id} → empty if there's no such loan OR it isn't the member's (→
	// 404 either way)
	public Optional<Loan> findLoan(Member current, int loanId) throws SQLException {
		if (current.role().equals(Roles.LIBRARIAN)) {
			return loanDao.findById(loanId); // any loan
		}
		return loanDao.findByIdForMember(loanId, current.memberId()); // own loans only
	}

	// POST /loans: check out a book 
	// The route passes the current member's id, from the session, never from the
	// body.
	// NoSuchElementException: no such book. IllegalStateException: already out.
	//
	// Deliberately naive; two problems are left for Module 6:
	// 1. Not all-or-nothing: if setAvailable failed after the insert, there would
	// be
	// a loan for a book still marked available.
	// 2. Not safe under concurrency: two requests can both read available = TRUE
	// before either one updates it, and both check out the same book.
	public Loan checkOut(int memberId, int bookId) throws SQLException {
		Optional<Book> book = bookDao.findById(bookId);
		if (book.isEmpty()) {
			throw new NoSuchElementException("No book " + bookId);
		}
		if (!book.get().available()) {
			throw new IllegalStateException("Book " + bookId + " is checked out");
		}
		LocalDate today = LocalDate.now();
		int loanId = loanDao.insert(new Loan(null, bookId, memberId, today, today.plusDays(LOAN_DAYS), null));
		bookDao.setAvailable(bookId, false);
		Optional<Loan> created = loanDao.findById(loanId);
		if (created.isEmpty()) {
			throw new IllegalStateException("Loan " + loanId + " was inserted but can't be found");
		}
		return created.get();
	}

	// PUT /loans/{id}/return → the returned loan, or empty if it's not found or not
	// the member's.
	// Idempotent: returning an already-returned loan changes nothing and still
	// succeeds.
	public Optional<Loan> returnBook(Member current, int loanId) throws SQLException {
		Optional<Loan> loan = findLoan(current, loanId);
		if (loan.isEmpty()) {
			return Optional.empty();
		}
		if (loan.get().returnedOn() != null) {
			return loan; // already returned
		}
		if (loanDao.markReturned(loanId, LocalDate.now())) {
			bookDao.setAvailable(loan.get().bookId(), true);
		}
		return loanDao.findById(loanId);
	}
}
