DiscountApprovalService.java

package com.university.bookstore.chain;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.university.bookstore.model.Material;

/**
 * Service for managing discount approval using the Chain of Responsibility pattern.
 * Builds and manages the approval chain and processes discount requests.
 * 
 * <p>This service demonstrates the Chain of Responsibility pattern by creating
 * a hierarchy of approval handlers and processing requests through the chain.</p>
 * 
 * @author Navid Mohaghegh
 * @version 3.0
 * @since 2024-09-15
 */
public class DiscountApprovalService {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(DiscountApprovalService.class);
    private final DiscountHandler chain;
    private final List<DiscountRequest> processedRequests;
    
    /**
     * Creates a new discount approval service with the default chain.
     */
    public DiscountApprovalService() {
        this.chain = buildDefaultChain();
        this.processedRequests = new ArrayList<>();
    }
    
    /**
     * Creates a new discount approval service with a custom chain.
     * 
     * @param chain the approval chain to use
     */
    public DiscountApprovalService(DiscountHandler chain) {
        this.chain = Objects.requireNonNull(chain, "Chain cannot be null");
        this.processedRequests = new ArrayList<>();
    }
    
    /**
     * Processes a discount request through the approval chain.
     * 
     * @param material the material for which discount is requested
     * @param discountPercent the discount percentage (0.0 to 100.0)
     * @param customerId the customer requesting the discount
     * @param reason the reason for the discount request
     * @return the processed discount request
     */
    public DiscountRequest requestDiscount(Material material, double discountPercent, 
                                         String customerId, String reason) {
        if (material == null) {
            throw new IllegalArgumentException("Material cannot be null");
        }
        if (customerId == null || customerId.trim().isEmpty()) {
            throw new IllegalArgumentException("Customer ID cannot be null or empty");
        }
        if (reason == null || reason.trim().isEmpty()) {
            throw new IllegalArgumentException("Reason cannot be null or empty");
        }
        
        // Convert percentage to decimal
        double discountRate = discountPercent / 100.0;
        
        DiscountRequest request = new DiscountRequest(material, discountRate, customerId, reason);
        
        LOGGER.info("Processing discount request:");
        LOGGER.info("Material: {}", material.getTitle());
        LOGGER.info("Requested discount: {}%", discountPercent);
        LOGGER.info("Customer: {}", customerId);
        LOGGER.info("Reason: {}", reason);
        LOGGER.info("---");
        
        // Process through the chain
        chain.handleRequest(request);
        
        // Store the processed request
        processedRequests.add(request);
        
        // Print result
        if (request.isApproved()) {
            LOGGER.info("// [OK] APPROVED by {}", request.getApprovedBy());
            LOGGER.info("Final price: ${}", String.format("%.2f", request.getDiscountedPrice()));
            LOGGER.info("Savings: ${}", String.format("%.2f", request.getSavingsAmount()));
        } else {
            LOGGER.info("[X] REJECTED: {}", request.getRejectionReason());
        }
        
        return request;
    }
    
    /**
     * Calculates the final price for an approved discount request.
     * 
     * @param request the discount request
     * @return the final price
     */
    public double calculateFinalPrice(DiscountRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");
        }
        
        return request.getDiscountedPrice();
    }
    
    /**
     * Gets all processed discount requests.
     * 
     * @return list of processed requests
     */
    public List<DiscountRequest> getProcessedRequests() {
        return new ArrayList<>(processedRequests);
    }
    
    /**
     * Gets approved discount requests.
     * 
     * @return list of approved requests
     */
    public List<DiscountRequest> getApprovedRequests() {
        return processedRequests.stream()
            .filter(DiscountRequest::isApproved)
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }
    
    /**
     * Gets rejected discount requests.
     * 
     * @return list of rejected requests
     */
    public List<DiscountRequest> getRejectedRequests() {
        return processedRequests.stream()
            .filter(request -> !request.isApproved())
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }
    
    /**
     * Gets discount requests for a specific customer.
     * 
     * @param customerId the customer ID
     * @return list of requests for the customer
     */
    public List<DiscountRequest> getRequestsForCustomer(String customerId) {
        return processedRequests.stream()
            .filter(request -> Objects.equals(request.getCustomerId(), customerId))
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }
    
    /**
     * Gets discount requests for a specific material.
     * 
     * @param material the material
     * @return list of requests for the material
     */
    public List<DiscountRequest> getRequestsForMaterial(Material material) {
        return processedRequests.stream()
            .filter(request -> Objects.equals(request.getMaterial(), material))
            .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
    }
    
    /**
     * Gets approval statistics.
     * 
     * @return approval statistics
     */
    public ApprovalStats getApprovalStats() {
        int totalRequests = processedRequests.size();
        int approvedRequests = (int) processedRequests.stream()
            .filter(DiscountRequest::isApproved)
            .count();
        int rejectedRequests = totalRequests - approvedRequests;
        
        double approvalRate = totalRequests > 0 ? (double) approvedRequests / totalRequests : 0.0;
        
        double totalSavings = processedRequests.stream()
            .filter(DiscountRequest::isApproved)
            .mapToDouble(DiscountRequest::getSavingsAmount)
            .sum();
        
        return new ApprovalStats(
            totalRequests,
            approvedRequests,
            rejectedRequests,
            approvalRate,
            totalSavings
        );
    }
    
    /**
     * Clears all processed requests.
     */
    public void clearRequests() {
        processedRequests.clear();
    }
    
    /**
     * Gets the approval chain information.
     * 
     * @return chain information
     */
    public String getChainInfo() {
        return chain.getHandlerInfo();
    }
    
    /**
     * Builds the default approval chain: Manager -> Director -> VP.
     * 
     * @return the default approval chain
     */
    private DiscountHandler buildDefaultChain() {
        DiscountHandler manager = new ManagerHandler();
        DiscountHandler director = new DirectorHandler();
        DiscountHandler vp = new VPHandler();
        
        // Set up the chain
        manager.setNext(director);
        director.setNext(vp);
        
        return manager;
    }
    
    /**
     * Statistics class for approval analysis.
     */
    public static class ApprovalStats {
        private final int totalRequests;
        private final int approvedRequests;
        private final int rejectedRequests;
        private final double approvalRate;
        private final double totalSavings;
        
        public ApprovalStats(int totalRequests, int approvedRequests, int rejectedRequests,
                           double approvalRate, double totalSavings) {
            this.totalRequests = totalRequests;
            this.approvedRequests = approvedRequests;
            this.rejectedRequests = rejectedRequests;
            this.approvalRate = approvalRate;
            this.totalSavings = totalSavings;
        }
        
        public int getTotalRequests() { return totalRequests; }
        public int getApprovedRequests() { return approvedRequests; }
        public int getRejectedRequests() { return rejectedRequests; }
        public double getApprovalRate() { return approvalRate; }
        public double getTotalSavings() { return totalSavings; }
        
        @Override
        public String toString() {
            return String.format("ApprovalStats[Total=%d, Approved=%d, Rejected=%d, Rate=%.1f%%, Savings=$%.2f]",
                totalRequests, approvedRequests, rejectedRequests, approvalRate * 100, totalSavings);
        }
    }
    
    @Override
    public String toString() {
        return String.format("DiscountApprovalService[Chain=%s, Requests=%d]",
            chain.getHandlerName(), processedRequests.size());
    }
}