package com.mzh.library.dao.impl;

import com.mzh.library.dao.ReportDao;
import com.mzh.library.entity.BookStatus;
import com.mzh.library.entity.BorrowRecordStatus;
import com.mzh.library.entity.BorrowingSummary;
import com.mzh.library.entity.InventorySummary;
import com.mzh.library.entity.OverdueReportRow;
import com.mzh.library.entity.PopularBookReportRow;
import com.mzh.library.exception.DaoException;
import com.mzh.library.util.JdbcUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class JdbcReportDao implements ReportDao {
    private static final String INVENTORY_SUMMARY = ""
        + "SELECT COUNT(*) AS total_titles, "
        + "COALESCE(SUM(total_copies), 0) AS total_copies, "
        + "COALESCE(SUM(available_copies), 0) AS available_copies, "
        + "COALESCE(SUM(CASE WHEN status <> ? OR available_copies <= 0 THEN 1 ELSE 0 END), 0) "
        + "AS unavailable_or_empty_titles "
        + "FROM books";

    private static final String BORROWING_SUMMARY = ""
        + "SELECT "
        + "COALESCE(SUM(CASE WHEN status = ? AND returned_at IS NULL THEN 1 ELSE 0 END), 0) AS active_loans, "
        + "COALESCE(SUM(CASE WHEN status = ? THEN 1 ELSE 0 END), 0) AS returned_loans, "
        + "COALESCE(SUM(CASE WHEN status = ? AND returned_at IS NULL AND due_at < CURRENT_TIMESTAMP "
        + "THEN 1 ELSE 0 END), 0) AS overdue_loans "
        + "FROM borrow_records";

    private static final String OVERDUE_ROWS = ""
        + "SELECT r.reader_identifier, r.full_name AS reader_name, "
        + "b.book_identifier, b.title AS book_title, br.due_at, "
        + "GREATEST(DATEDIFF(CURRENT_DATE, DATE(br.due_at)), 0) AS overdue_days "
        + "FROM borrow_records br "
        + "JOIN readers r ON r.id = br.reader_id "
        + "JOIN books b ON b.id = br.book_id "
        + "WHERE br.status = ? AND br.returned_at IS NULL AND br.due_at < CURRENT_TIMESTAMP "
        + "ORDER BY br.due_at, r.reader_identifier, b.book_identifier";

    private static final String POPULAR_BOOKS = ""
        + "SELECT b.book_identifier, b.title, b.author, COUNT(br.id) AS borrow_count "
        + "FROM books b "
        + "JOIN borrow_records br ON br.book_id = b.id "
        + "GROUP BY b.id, b.book_identifier, b.title, b.author "
        + "ORDER BY borrow_count DESC, b.title, b.book_identifier "
        + "LIMIT ?";

    @Override
    public InventorySummary loadInventorySummary() {
        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(INVENTORY_SUMMARY)) {
            statement.setString(1, BookStatus.AVAILABLE.getCode());
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    InventorySummary summary = new InventorySummary();
                    summary.setTotalTitles(resultSet.getInt("total_titles"));
                    summary.setTotalCopies(resultSet.getInt("total_copies"));
                    summary.setAvailableCopies(resultSet.getInt("available_copies"));
                    summary.setUnavailableOrEmptyTitles(resultSet.getInt("unavailable_or_empty_titles"));
                    return summary;
                }
                return new InventorySummary();
            }
        } catch (SQLException ex) {
            throw new DaoException("Unable to load inventory report summary", ex);
        }
    }

    @Override
    public BorrowingSummary loadBorrowingSummary() {
        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(BORROWING_SUMMARY)) {
            statement.setString(1, BorrowRecordStatus.ACTIVE.getCode());
            statement.setString(2, BorrowRecordStatus.RETURNED.getCode());
            statement.setString(3, BorrowRecordStatus.ACTIVE.getCode());
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    BorrowingSummary summary = new BorrowingSummary();
                    summary.setActiveLoans(resultSet.getInt("active_loans"));
                    summary.setReturnedLoans(resultSet.getInt("returned_loans"));
                    summary.setOverdueLoans(resultSet.getInt("overdue_loans"));
                    return summary;
                }
                return new BorrowingSummary();
            }
        } catch (SQLException ex) {
            throw new DaoException("Unable to load borrowing report summary", ex);
        }
    }

    @Override
    public List<OverdueReportRow> findOverdueRows() {
        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(OVERDUE_ROWS)) {
            statement.setString(1, BorrowRecordStatus.ACTIVE.getCode());
            try (ResultSet resultSet = statement.executeQuery()) {
                List<OverdueReportRow> rows = new ArrayList<>();
                while (resultSet.next()) {
                    rows.add(mapOverdueRow(resultSet));
                }
                return rows;
            }
        } catch (SQLException ex) {
            throw new DaoException("Unable to load overdue report rows", ex);
        }
    }

    @Override
    public List<PopularBookReportRow> findPopularBooks(int limit) {
        try (Connection connection = JdbcUtil.getConnection();
             PreparedStatement statement = connection.prepareStatement(POPULAR_BOOKS)) {
            statement.setInt(1, limit);
            try (ResultSet resultSet = statement.executeQuery()) {
                List<PopularBookReportRow> rows = new ArrayList<>();
                while (resultSet.next()) {
                    rows.add(mapPopularBook(resultSet));
                }
                return rows;
            }
        } catch (SQLException ex) {
            throw new DaoException("Unable to load popular book report rows", ex);
        }
    }

    private OverdueReportRow mapOverdueRow(ResultSet resultSet) throws SQLException {
        OverdueReportRow row = new OverdueReportRow();
        row.setReaderIdentifier(resultSet.getString("reader_identifier"));
        row.setReaderName(resultSet.getString("reader_name"));
        row.setBookIdentifier(resultSet.getString("book_identifier"));
        row.setBookTitle(resultSet.getString("book_title"));
        row.setDueAt(toLocalDateTime(resultSet.getTimestamp("due_at")));
        row.setOverdueDays(resultSet.getLong("overdue_days"));
        return row;
    }

    private PopularBookReportRow mapPopularBook(ResultSet resultSet) throws SQLException {
        PopularBookReportRow row = new PopularBookReportRow();
        row.setBookIdentifier(resultSet.getString("book_identifier"));
        row.setTitle(resultSet.getString("title"));
        row.setAuthor(resultSet.getString("author"));
        row.setBorrowCount(resultSet.getInt("borrow_count"));
        return row;
    }

    private LocalDateTime toLocalDateTime(Timestamp timestamp) {
        return timestamp == null ? null : timestamp.toLocalDateTime();
    }
}
