package com.mzh.library.service;

import com.mzh.library.dao.ReportDao;
import com.mzh.library.entity.AuthenticatedUser;
import com.mzh.library.entity.BorrowingSummary;
import com.mzh.library.entity.InventorySummary;
import com.mzh.library.entity.OverdueReportRow;
import com.mzh.library.entity.Permission;
import com.mzh.library.entity.PopularBookReportRow;
import com.mzh.library.entity.ReportCenter;
import com.mzh.library.entity.Role;
import com.mzh.library.exception.DaoException;
import com.mzh.library.service.impl.ReportServiceImpl;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public final class ReportServiceCheck {
    private static final String DENIED_MESSAGE = "您无权查看报表。";
    private static final String UNAVAILABLE_MESSAGE =
        "报表服务暂时不可用，请稍后重试。";

    private ReportServiceCheck() {
    }

    public static void main(String[] args) {
        Logger.getLogger(ReportServiceImpl.class.getName()).setLevel(Level.OFF);

        ReportService service = new ReportServiceImpl(new InMemoryReportDao());
        AuthenticatedUser administrator = user(5L, Role.ADMINISTRATOR);
        AuthenticatedUser librarian = user(10L, Role.LIBRARIAN);
        AuthenticatedUser reader = user(20L, Role.READER);

        ServiceResult<ReportCenter> anonymousDenied = service.loadReportCenter(null);
        requireDenied(anonymousDenied, "anonymous user should not view reports");

        ServiceResult<ReportCenter> readerDenied = service.loadReportCenter(reader);
        requireDenied(readerDenied, "reader should not view reports");

        ServiceResult<ReportCenter> adminReport = service.loadReportCenter(administrator);
        require(adminReport.isSuccessful(), "administrator should load report center");

        ServiceResult<ReportCenter> report = service.loadReportCenter(librarian);
        require(report.isSuccessful(), "librarian should load report center");
        require(report.getData().getInventorySummary().getTotalTitles() == 4,
            "inventory summary should expose total titles");
        require(report.getData().getInventorySummary().getUnavailableOrEmptyTitles() == 2,
            "inventory summary should expose unavailable or empty titles");
        require(report.getData().getBorrowingSummary().getActiveLoans() == 3,
            "borrowing summary should expose active loans");
        require(report.getData().getBorrowingSummary().getOverdueLoans() == 1,
            "borrowing summary should expose overdue loans");
        require(report.getData().getOverdueRows().size() == 1,
            "report center should include overdue rows");
        require(report.getData().getPopularBooks().size() == 2,
            "report center should include popular book rows");

        ReportService failingService = new ReportServiceImpl(new FailingReportDao());
        ServiceResult<ReportCenter> failure = failingService.loadReportCenter(librarian);
        require(!failure.isSuccessful(), "DAO failure should not escape reports");
        require(UNAVAILABLE_MESSAGE.equals(failure.getMessage()),
            "DAO failure should map to safe report message");
    }

    private static void requireDenied(ServiceResult<ReportCenter> result, String message) {
        require(!result.isSuccessful(), message);
        require(DENIED_MESSAGE.equals(result.getMessage()),
            "report denial should use the report permission message");
    }

    private static AuthenticatedUser user(long id, Role role) {
        return new AuthenticatedUser(id, role.getCode(), role.getDisplayName(), role,
            role == Role.READER
                ? EnumSet.of(Permission.VIEW_CATALOG, Permission.BORROW_BOOKS)
                : EnumSet.of(Permission.MANAGE_BORROWING, Permission.VIEW_REPORTS, Permission.VIEW_CATALOG));
    }

    private static void require(boolean condition, String message) {
        if (!condition) {
            throw new AssertionError(message);
        }
    }

    private static final class InMemoryReportDao implements ReportDao {
        @Override
        public InventorySummary loadInventorySummary() {
            InventorySummary summary = new InventorySummary();
            summary.setTotalTitles(4);
            summary.setTotalCopies(12);
            summary.setAvailableCopies(7);
            summary.setUnavailableOrEmptyTitles(2);
            return summary;
        }

        @Override
        public BorrowingSummary loadBorrowingSummary() {
            BorrowingSummary summary = new BorrowingSummary();
            summary.setActiveLoans(3);
            summary.setReturnedLoans(5);
            summary.setOverdueLoans(1);
            return summary;
        }

        @Override
        public List<OverdueReportRow> findOverdueRows() {
            OverdueReportRow row = new OverdueReportRow();
            row.setReaderIdentifier("RD-1000");
            row.setReaderName("Active Reader");
            row.setBookIdentifier("BK-1000");
            row.setBookTitle("Effective Java");
            row.setDueAt(LocalDateTime.of(2026, 4, 1, 12, 0));
            row.setOverdueDays(26);
            return Collections.singletonList(row);
        }

        @Override
        public List<PopularBookReportRow> findPopularBooks(int limit) {
            PopularBookReportRow first = popularBook("BK-1000", "Effective Java", "Joshua Bloch", 5);
            PopularBookReportRow second = popularBook("BK-1001", "Clean Code", "Robert C. Martin", 3);
            return Arrays.asList(first, second);
        }

        private PopularBookReportRow popularBook(String identifier, String title, String author, int borrowCount) {
            PopularBookReportRow row = new PopularBookReportRow();
            row.setBookIdentifier(identifier);
            row.setTitle(title);
            row.setAuthor(author);
            row.setBorrowCount(borrowCount);
            return row;
        }
    }

    private static final class FailingReportDao implements ReportDao {
        @Override
        public InventorySummary loadInventorySummary() {
            throw new DaoException("Simulated inventory report failure", null);
        }

        @Override
        public BorrowingSummary loadBorrowingSummary() {
            throw new DaoException("Simulated borrowing report failure", null);
        }

        @Override
        public List<OverdueReportRow> findOverdueRows() {
            throw new DaoException("Simulated overdue report failure", null);
        }

        @Override
        public List<PopularBookReportRow> findPopularBooks(int limit) {
            throw new DaoException("Simulated popular report failure", null);
        }
    }
}
