package com.mzh.library.service.impl;

import com.mzh.library.dao.BorrowRecordDao;
import com.mzh.library.entity.AuthenticatedUser;
import com.mzh.library.entity.Book;
import com.mzh.library.entity.BookStatus;
import com.mzh.library.entity.BorrowRecord;
import com.mzh.library.entity.BorrowRecordSearchCriteria;
import com.mzh.library.entity.BorrowRecordStatus;
import com.mzh.library.entity.Permission;
import com.mzh.library.entity.Reader;
import com.mzh.library.entity.ReaderStatus;
import com.mzh.library.entity.Role;
import com.mzh.library.exception.DaoException;
import com.mzh.library.service.BorrowingService;
import com.mzh.library.service.PermissionPolicy;
import com.mzh.library.service.ServiceResult;
import com.mzh.library.util.JdbcUtil;

import java.sql.SQLException;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;

public class BorrowingServiceImpl implements BorrowingService {
    public interface TransactionExecutor {
        <T> T execute(JdbcUtil.TransactionCallback<T> callback);
    }

    private static final Logger LOGGER = Logger.getLogger(BorrowingServiceImpl.class.getName());
    private static final String UNAVAILABLE_MESSAGE =
        "借阅服务暂时不可用，请稍后重试。";
    private static final String VALIDATION_MESSAGE = "请修正高亮的借阅字段。";
    private static final String DENIED_MESSAGE = "您无权管理借阅。";
    private static final String HISTORY_DENIED_MESSAGE = "您无权查看借阅历史。";
    private static final int LOAN_DAYS = 14;
    private static final int MAX_RENEWALS = 1;

    private final BorrowRecordDao borrowRecordDao;
    private final PermissionPolicy permissionPolicy;
    private final Clock clock;
    private final TransactionExecutor transactionExecutor;

    public BorrowingServiceImpl(BorrowRecordDao borrowRecordDao) {
        this(borrowRecordDao, new PermissionPolicy(), Clock.systemDefaultZone(), new JdbcTransactionExecutor());
    }

    public BorrowingServiceImpl(BorrowRecordDao borrowRecordDao, PermissionPolicy permissionPolicy, Clock clock,
                                TransactionExecutor transactionExecutor) {
        this.borrowRecordDao = borrowRecordDao;
        this.permissionPolicy = permissionPolicy;
        this.clock = clock;
        this.transactionExecutor = transactionExecutor;
    }

    @Override
    public ServiceResult<List<BorrowRecord>> searchRecords(AuthenticatedUser actor, BorrowRecordSearchCriteria criteria) {
        if (!canManageBorrowing(actor)) {
            return ServiceResult.failure(DENIED_MESSAGE);
        }

        BorrowRecordSearchCriteria normalized = criteria == null ? new BorrowRecordSearchCriteria() : criteria;
        Map<String, String> errors = validateSearch(normalized);
        if (!errors.isEmpty()) {
            return ServiceResult.validationFailure("请修正借阅检索筛选条件。", errors);
        }

        try {
            return ServiceResult.success(borrowRecordDao.search(normalized));
        } catch (DaoException ex) {
            LOGGER.log(Level.SEVERE, "Unable to search borrow records actorId=" + actor.getId(), ex);
            return ServiceResult.failure(UNAVAILABLE_MESSAGE);
        }
    }

    @Override
    public ServiceResult<Long> borrowBook(AuthenticatedUser actor, String readerIdentifier, String bookIdentifier) {
        if (!canManageBorrowing(actor)) {
            return ServiceResult.failure(DENIED_MESSAGE);
        }

        String normalizedReaderIdentifier = trim(readerIdentifier);
        String normalizedBookIdentifier = trim(bookIdentifier);
        Map<String, String> errors = validateBorrowIdentifiers(normalizedReaderIdentifier, normalizedBookIdentifier);
        if (!errors.isEmpty()) {
            return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
        }

        try {
            return transactionExecutor.execute(connection -> {
                Map<String, String> transactionErrors = new LinkedHashMap<>();
                Optional<Reader> readerResult = borrowRecordDao.findReaderByIdentifierForUpdate(connection,
                    normalizedReaderIdentifier);
                if (!readerResult.isPresent()) {
                    transactionErrors.put("readerIdentifier", "未找到读者。");
                }

                Optional<Book> bookResult = borrowRecordDao.findBookByIdentifierForUpdate(connection,
                    normalizedBookIdentifier);
                if (!bookResult.isPresent()) {
                    transactionErrors.put("bookIdentifier", "未找到图书。");
                }

                if (!transactionErrors.isEmpty()) {
                    return ServiceResult.validationFailure(VALIDATION_MESSAGE, transactionErrors);
                }

                Reader reader = readerResult.get();
                Book book = bookResult.get();
                validateBorrowEligibility(transactionErrors, reader, book, connection);
                if (!transactionErrors.isEmpty()) {
                    return ServiceResult.validationFailure(VALIDATION_MESSAGE, transactionErrors);
                }

                LocalDateTime borrowedAt = now();
                BorrowRecord record = new BorrowRecord();
                record.setReaderId(reader.getId());
                record.setBookId(book.getId());
                record.setBorrowedAt(borrowedAt);
                record.setDueAt(borrowedAt.plusDays(LOAN_DAYS));
                record.setRenewalCount(0);
                record.setStatus(BorrowRecordStatus.ACTIVE);

                long id = borrowRecordDao.create(connection, record);
                if (!borrowRecordDao.decrementAvailableCopies(connection, book.getId())) {
                    throw new DaoException("Book inventory was not decremented for borrow record " + id, null);
                }

                LOGGER.info("Borrowed book recordId=" + id + " readerId=" + reader.getId()
                    + " bookId=" + book.getId() + " actorId=" + actor.getId());
                return ServiceResult.success(id, "图书已借出。");
            });
        } catch (DaoException ex) {
            LOGGER.log(Level.SEVERE, "Unable to borrow book actorId=" + actor.getId()
                + " readerIdentifier=" + normalizedReaderIdentifier
                + " bookIdentifier=" + normalizedBookIdentifier, ex);
            return ServiceResult.failure(UNAVAILABLE_MESSAGE);
        }
    }

    @Override
    public ServiceResult<Void> returnBook(AuthenticatedUser actor, long recordId) {
        if (!canManageBorrowing(actor)) {
            return ServiceResult.failure(DENIED_MESSAGE);
        }
        if (recordId <= 0) {
            return ServiceResult.failure("请选择有效的借阅记录。");
        }

        try {
            return transactionExecutor.execute(connection -> {
                Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId);
                if (!recordResult.isPresent()) {
                    return ServiceResult.failure("未找到借阅记录。");
                }

                BorrowRecord record = recordResult.get();
                Map<String, String> errors = validateActiveLoan(record);
                if (!errors.isEmpty()) {
                    return ServiceResult.validationFailure("借阅记录不能归还。", errors);
                }

                if (!borrowRecordDao.markReturned(connection, recordId, now())) {
                    throw new DaoException("Borrow record was not marked returned: " + recordId, null);
                }
                borrowRecordDao.incrementAvailableCopies(connection, record.getBookId());

                LOGGER.info("Returned borrow recordId=" + recordId + " actorId=" + actor.getId());
                return ServiceResult.success(null, "图书已归还。");
            });
        } catch (DaoException ex) {
            LOGGER.log(Level.SEVERE, "Unable to return borrow record id=" + recordId + " actorId=" + actor.getId(), ex);
            return ServiceResult.failure(UNAVAILABLE_MESSAGE);
        }
    }

    @Override
    public ServiceResult<Void> renewLoan(AuthenticatedUser actor, long recordId) {
        if (!canManageBorrowing(actor)) {
            return ServiceResult.failure(DENIED_MESSAGE);
        }
        if (recordId <= 0) {
            return ServiceResult.failure("请选择有效的借阅记录。");
        }

        try {
            return transactionExecutor.execute(connection -> {
                Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId);
                if (!recordResult.isPresent()) {
                    return ServiceResult.failure("未找到借阅记录。");
                }

                BorrowRecord record = recordResult.get();
                Map<String, String> errors = validateActiveLoan(record);
                if (record.getRenewalCount() >= MAX_RENEWALS) {
                    errors.put("renewalCount", "该借阅已达到续借次数上限。");
                }
                if (!errors.isEmpty()) {
                    return ServiceResult.validationFailure("借阅记录不能续借。", errors);
                }

                LocalDateTime currentDueAt = record.getDueAt() == null ? now() : record.getDueAt();
                LocalDateTime newDueAt = currentDueAt.plusDays(LOAN_DAYS);
                if (!borrowRecordDao.renew(connection, recordId, newDueAt)) {
                    throw new DaoException("Borrow record was not renewed: " + recordId, null);
                }

                LOGGER.info("Renewed borrow recordId=" + recordId + " actorId=" + actor.getId());
                return ServiceResult.success(null, "借阅已续借。");
            });
        } catch (DaoException ex) {
            LOGGER.log(Level.SEVERE, "Unable to renew borrow record id=" + recordId + " actorId=" + actor.getId(), ex);
            return ServiceResult.failure(UNAVAILABLE_MESSAGE);
        }
    }

    @Override
    public ServiceResult<List<BorrowRecord>> listCurrentReaderHistory(AuthenticatedUser actor) {
        if (!canViewOwnHistory(actor)) {
            return ServiceResult.failure(HISTORY_DENIED_MESSAGE);
        }

        try {
            Optional<Reader> readerResult = borrowRecordDao.findReaderByUserId(actor.getId());
            if (!readerResult.isPresent()) {
                return ServiceResult.success(Collections.emptyList(), "您的账户未关联读者档案。");
            }

            return ServiceResult.success(borrowRecordDao.findByReaderId(readerResult.get().getId()));
        } catch (DaoException ex) {
            LOGGER.log(Level.SEVERE, "Unable to load reader loan history actorId=" + actor.getId(), ex);
            return ServiceResult.failure(UNAVAILABLE_MESSAGE);
        }
    }

    public int getMaxRenewals() {
        return MAX_RENEWALS;
    }

    private void validateBorrowEligibility(Map<String, String> errors, Reader reader, Book book,
                                           java.sql.Connection connection) {
        if (reader.getStatus() != ReaderStatus.ACTIVE) {
            errors.put("readerIdentifier", "读者状态必须为正常才能借阅图书。");
        }
        int activeLoans = borrowRecordDao.countActiveByReaderId(connection, reader.getId());
        if (activeLoans >= reader.getMaxBorrowCount()) {
            errors.put("readerIdentifier", "读者已达到在借数量上限。");
        }
        if (book.getStatus() != BookStatus.AVAILABLE) {
            errors.put("bookIdentifier", "图书状态不允许借阅。");
        } else if (book.getAvailableCopies() <= 0) {
            errors.put("bookIdentifier", "该图书没有可借副本。");
        }
    }

    private Map<String, String> validateSearch(BorrowRecordSearchCriteria criteria) {
        Map<String, String> errors = new LinkedHashMap<>();
        String statusCode = trim(criteria.getStatusCode());
        criteria.setReaderIdentifier(criteria.getReaderIdentifier());
        criteria.setBookIdentifier(criteria.getBookIdentifier());
        criteria.setStatusCode(statusCode);

        if (!statusCode.isEmpty() && !BorrowRecordSearchCriteria.OVERDUE_STATUS.equals(statusCode)) {
            try {
                BorrowRecordStatus.fromCode(statusCode);
            } catch (IllegalArgumentException ex) {
                errors.put("status", "请选择有效的借阅状态。");
            }
        }
        return errors;
    }

    private Map<String, String> validateBorrowIdentifiers(String readerIdentifier, String bookIdentifier) {
        Map<String, String> errors = new LinkedHashMap<>();
        requireLength(errors, "readerIdentifier", readerIdentifier, "读者编号", 64);
        requireLength(errors, "bookIdentifier", bookIdentifier, "图书编号", 64);
        return errors;
    }

    private Map<String, String> validateActiveLoan(BorrowRecord record) {
        Map<String, String> errors = new LinkedHashMap<>();
        if (record.getStatus() != BorrowRecordStatus.ACTIVE || record.getReturnedAt() != null) {
            errors.put("status", "只有借阅中的记录可以执行此操作。");
        }
        return errors;
    }

    private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) {
        if (value == null || value.isEmpty()) {
            errors.put(field, "请填写" + label + "。");
            return;
        }
        if (value.length() > maxLength) {
            errors.put(field, label + "不能超过 " + maxLength + " 个字符。");
        }
    }

    private boolean canManageBorrowing(AuthenticatedUser actor) {
        return actor != null && permissionPolicy.allows(actor.getRole(), Permission.MANAGE_BORROWING);
    }

    private boolean canViewOwnHistory(AuthenticatedUser actor) {
        return actor != null
            && actor.getRole() == Role.READER
            && permissionPolicy.allows(actor.getRole(), Permission.BORROW_BOOKS);
    }

    private LocalDateTime now() {
        return LocalDateTime.now(clock);
    }

    private String trim(String value) {
        return value == null ? "" : value.trim();
    }

    private static final class JdbcTransactionExecutor implements TransactionExecutor {
        @Override
        public <T> T execute(JdbcUtil.TransactionCallback<T> callback) {
            return JdbcUtil.executeInTransaction(callback);
        }
    }

    public static final class DirectTransactionExecutor implements TransactionExecutor {
        @Override
        public <T> T execute(JdbcUtil.TransactionCallback<T> callback) {
            try {
                return callback.execute(null);
            } catch (SQLException ex) {
                throw new DaoException("Unable to execute direct transaction", ex);
            }
        }
    }
}
