package com.mzh.library.controller;

import com.mzh.library.dao.impl.JdbcBookDao;
import com.mzh.library.dao.impl.JdbcBorrowRecordDao;
import com.mzh.library.dao.impl.JdbcReaderDao;
import com.mzh.library.dao.impl.JdbcReportDao;
import com.mzh.library.entity.AuthenticatedUser;
import com.mzh.library.entity.Book;
import com.mzh.library.entity.BookCategory;
import com.mzh.library.entity.BookSearchCriteria;
import com.mzh.library.entity.BookStatus;
import com.mzh.library.entity.BorrowRecord;
import com.mzh.library.entity.BorrowRecordSearchCriteria;
import com.mzh.library.entity.BorrowingSummary;
import com.mzh.library.entity.InventorySummary;
import com.mzh.library.entity.Reader;
import com.mzh.library.entity.ReaderSearchCriteria;
import com.mzh.library.entity.ReportCenter;
import com.mzh.library.entity.Role;
import com.mzh.library.service.BookService;
import com.mzh.library.service.BorrowingService;
import com.mzh.library.service.ReaderService;
import com.mzh.library.service.ReportService;
import com.mzh.library.service.ServiceResult;
import com.mzh.library.service.impl.BookServiceImpl;
import com.mzh.library.service.impl.BorrowingServiceImpl;
import com.mzh.library.service.impl.ReaderServiceImpl;
import com.mzh.library.service.impl.ReportServiceImpl;
import com.mzh.library.util.SessionAttributes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class DashboardServlet extends HttpServlet {
    private static final String DASHBOARD_JSP = "/WEB-INF/jsp/dashboard.jsp";

    private BookService bookService;
    private BorrowingService borrowingService;
    private ReaderService readerService;
    private ReportService reportService;

    @Override
    public void init() {
        this.bookService = new BookServiceImpl(new JdbcBookDao());
        this.borrowingService = new BorrowingServiceImpl(new JdbcBorrowRecordDao());
        this.readerService = new ReaderServiceImpl(new JdbcReaderDao());
        this.reportService = new ReportServiceImpl(new JdbcReportDao());
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        AuthenticatedUser user = currentUser(request);
        request.setAttribute("currentUser", user);

        ServiceResult<List<BookCategory>> categoryResult = bookService.listCategories();
        request.setAttribute("categories", categoryResult.isSuccessful()
            ? listOrEmpty(categoryResult.getData())
            : Collections.emptyList());
        if (!categoryResult.isSuccessful()) {
            setErrorMessage(request, categoryResult.getMessage());
        }

        ServiceResult<List<Book>> bookResult = bookService.searchBooks(new BookSearchCriteria());
        List<Book> dashboardBooks = bookResult.isSuccessful()
            ? listOrEmpty(bookResult.getData())
            : Collections.emptyList();
        request.setAttribute("dashboardBooks", dashboardBooks);
        if (!bookResult.isSuccessful()) {
            setErrorMessage(request, bookResult.getMessage());
        }

        List<DashboardMetric> metrics = Collections.emptyList();
        if (isStaff(user)) {
            Integer readerTotal = null;
            ServiceResult<List<Reader>> readerResult = readerService.searchReaders(new ReaderSearchCriteria());
            if (readerResult.isSuccessful()) {
                readerTotal = listOrEmpty(readerResult.getData()).size();
            } else {
                setErrorMessage(request, readerResult.getMessage());
            }

            ServiceResult<ReportCenter> reportResult = reportService.loadReportCenter(user);
            if (reportResult.isSuccessful()) {
                ReportCenter reportCenter = reportResult.getData();
                request.setAttribute("reportCenter", reportCenter);
                metrics = metricsFromReport(reportCenter, readerTotal);
            } else {
                setErrorMessage(request, reportResult.getMessage());
            }

            ServiceResult<List<BorrowRecord>> borrowResult =
                borrowingService.searchRecords(user, new BorrowRecordSearchCriteria());
            request.setAttribute("dashboardBorrowRecords", borrowResult.isSuccessful()
                ? listOrEmpty(borrowResult.getData())
                : Collections.emptyList());
            if (!borrowResult.isSuccessful()) {
                setErrorMessage(request, borrowResult.getMessage());
            }
        }

        if (metrics.isEmpty() && bookResult.isSuccessful()) {
            metrics = metricsFromBooks(dashboardBooks);
        }
        request.setAttribute("dashboardMetrics", metrics);
        request.getRequestDispatcher(DASHBOARD_JSP).forward(request, response);
    }

    private AuthenticatedUser currentUser(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        Object value = session == null ? null : session.getAttribute(SessionAttributes.AUTHENTICATED_USER);
        return value instanceof AuthenticatedUser ? (AuthenticatedUser) value : null;
    }

    private boolean isStaff(AuthenticatedUser user) {
        return user != null && (user.getRole() == Role.ADMINISTRATOR || user.getRole() == Role.LIBRARIAN);
    }

    private <T> List<T> listOrEmpty(List<T> values) {
        return values == null ? Collections.emptyList() : values;
    }

    private List<DashboardMetric> metricsFromReport(ReportCenter reportCenter, Integer readerTotal) {
        if (reportCenter == null) {
            return Collections.emptyList();
        }
        InventorySummary inventory = reportCenter.getInventorySummary();
        BorrowingSummary borrowing = reportCenter.getBorrowingSummary();
        List<DashboardMetric> metrics = new ArrayList<>();
        metrics.add(new DashboardMetric("馆藏总册", valueOf(inventory, MetricField.TOTAL_COPIES), "册", "来自报表中心"));
        metrics.add(new DashboardMetric("当前借出", valueOf(borrowing, MetricField.ACTIVE_LOANS), "册", "实时借阅记录"));
        metrics.add(new DashboardMetric("逾期借阅", valueOf(borrowing, MetricField.OVERDUE_LOANS), "册", "需跟进记录"));
        if (readerTotal == null) {
            metrics.add(new DashboardMetric("可借册数", valueOf(inventory, MetricField.AVAILABLE_COPIES),
                "册", "馆藏可借库存"));
        } else {
            metrics.add(new DashboardMetric("读者总数", readerTotal, "人", "实时读者档案"));
        }
        return metrics;
    }

    private List<DashboardMetric> metricsFromBooks(List<Book> books) {
        int totalTitles = 0;
        int totalCopies = 0;
        int availableCopies = 0;
        int unavailableOrEmptyTitles = 0;
        for (Book book : books) {
            totalTitles++;
            totalCopies += book.getTotalCopies();
            availableCopies += book.getAvailableCopies();
            if (book.getStatus() != BookStatus.AVAILABLE || book.getAvailableCopies() <= 0) {
                unavailableOrEmptyTitles++;
            }
        }

        List<DashboardMetric> metrics = new ArrayList<>();
        metrics.add(new DashboardMetric("图书种类", totalTitles, "种", "来自馆藏检索"));
        metrics.add(new DashboardMetric("馆藏总册", totalCopies, "册", "来自馆藏检索"));
        metrics.add(new DashboardMetric("可借册数", availableCopies, "册", "来自馆藏检索"));
        metrics.add(new DashboardMetric("需关注馆藏", unavailableOrEmptyTitles, "种", "不可借或无库存"));
        return metrics;
    }

    private int valueOf(InventorySummary summary, MetricField field) {
        if (summary == null) {
            return 0;
        }
        switch (field) {
            case TOTAL_COPIES:
                return summary.getTotalCopies();
            case AVAILABLE_COPIES:
                return summary.getAvailableCopies();
            default:
                return 0;
        }
    }

    private int valueOf(BorrowingSummary summary, MetricField field) {
        if (summary == null) {
            return 0;
        }
        switch (field) {
            case ACTIVE_LOANS:
                return summary.getActiveLoans();
            case OVERDUE_LOANS:
                return summary.getOverdueLoans();
            default:
                return 0;
        }
    }

    private void setErrorMessage(HttpServletRequest request, String message) {
        if (message != null && !message.isEmpty() && request.getAttribute("errorMessage") == null) {
            request.setAttribute("errorMessage", message);
        }
    }

    private enum MetricField {
        TOTAL_COPIES,
        AVAILABLE_COPIES,
        ACTIVE_LOANS,
        OVERDUE_LOANS
    }

    public static final class DashboardMetric {
        private final String label;
        private final int value;
        private final String unit;
        private final String note;

        private DashboardMetric(String label, int value, String unit, String note) {
            this.label = label;
            this.value = value;
            this.unit = unit;
            this.note = note;
        }

        public String getLabel() {
            return label;
        }

        public int getValue() {
            return value;
        }

        public String getUnit() {
            return unit;
        }

        public String getNote() {
            return note;
        }
    }
}
