# AuditLogObserver.java

    package com.university.bookstore.observer;

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

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

    /**
     * Observer that maintains an audit log of all material events.
     * Provides a complete history of system activities for compliance and debugging.
     *
     * @author Navid Mohaghegh
     * @version 3.0
     * @since 2024-09-15
     */
    public class AuditLogObserver implements MaterialObserver {

        private static final Logger LOGGER = LoggerFactory.getLogger(AuditLogObserver.class);

        private final List<AuditLogEntry> auditLog;
        private final int maxLogSize;

        /**
         * Creates a new audit log observer with unlimited log size.
         */
        public AuditLogObserver() {
            this(Integer.MAX_VALUE);
        }

        /**
         * Creates a new audit log observer with the specified maximum log size.
         *
         * @param maxLogSize the maximum number of log entries to keep
         */
        public AuditLogObserver(int maxLogSize) {
            this.auditLog = new ArrayList<>();
            this.maxLogSize = maxLogSize;
        }

        @Override
        public void onEvent(MaterialEvent event) {
            if (event == null) {
                return;
            }

            AuditLogEntry entry = new AuditLogEntry(
                event.getTimestamp(),
                event.getEventType(),
                event.getMaterial().getId(),
                event.getMaterial().getTitle(),
                event.getDescription()
            );

            auditLog.add(entry);

            // Maintain log size limit
            if (auditLog.size() > maxLogSize) {
                auditLog.remove(0); // Remove oldest entry
            }
        }

        /**
         * Gets all audit log entries.
         *
         * @return list of audit log entries
         */
        public List<AuditLogEntry> getAuditLog() {
            return new ArrayList<>(auditLog);
        }

        /**
         * Gets audit log entries for a specific material.
         *
         * @param materialId the material ID to filter by
         * @return list of audit log entries for the material
         */
        public List<AuditLogEntry> getAuditLogForMaterial(String materialId) {
            return auditLog.stream()
                .filter(entry -> Objects.equals(entry.getMaterialId(), materialId))
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        }

        /**
         * Gets audit log entries for a specific event type.
         *
         * @param eventType the event type to filter by
         * @return list of audit log entries for the event type
         */
        public List<AuditLogEntry> getAuditLogForEventType(String eventType) {
            return auditLog.stream()
                .filter(entry -> Objects.equals(entry.getEventType(), eventType))
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        }

        /**
         * Gets audit log entries within a time range.
         *
         * @param startTime the start time in milliseconds
         * @param endTime the end time in milliseconds
         * @return list of audit log entries within the time range
         */
        public List<AuditLogEntry> getAuditLogForTimeRange(long startTime, long endTime) {
            return auditLog.stream()
                .filter(entry -> entry.getTimestamp() >= startTime && entry.getTimestamp() <= endTime)
                .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
        }

        /**
         * Prints the audit log to the console.
         */
        public void printAuditLog() {
            auditLog.forEach(entry -> LOGGER.info("Audit: {}", entry.toString()));
        }

        /**
         * Gets the number of audit log entries.
         *
         * @return the log size
         */
        public int getLogSize() {
            return auditLog.size();
        }

        /**
         * Clears the audit log.
         */
        public void clearAuditLog() {
            auditLog.clear();
        }

        /**
         * Gets audit log statistics.
         *
         * @return audit log statistics
         */
        public AuditLogStats getAuditLogStats() {
            int totalEntries = auditLog.size();
            int materialAddedCount = (int) auditLog.stream()
                .filter(entry -> "MATERIAL_ADDED".equals(entry.getEventType()))
                .count();
            int priceChangedCount = (int) auditLog.stream()
                .filter(entry -> "PRICE_CHANGED".equals(entry.getEventType()))
                .count();

            long oldestTimestamp = auditLog.stream()
                .mapToLong(AuditLogEntry::getTimestamp)
                .min()
                .orElse(0L);

            long newestTimestamp = auditLog.stream()
                .mapToLong(AuditLogEntry::getTimestamp)
                .max()
                .orElse(0L);

            return new AuditLogStats(
                totalEntries,
                materialAddedCount,
                priceChangedCount,
                oldestTimestamp,
                newestTimestamp
            );
        }

        @Override
        public String getObserverName() {
            return "AuditLogObserver";
        }

        /**
         * Represents a single audit log entry.
         */
        public static class AuditLogEntry {
            private final long timestamp;
            private final String eventType;
            private final String materialId;
            private final String materialTitle;
            private final String description;

            /**
             * Creates an audit log entry.
             *
             * @param timestamp when the event occurred
             * @param eventType type of event
             * @param materialId ID of the material
             * @param materialTitle title of the material
             * @param description event description
             */
            public AuditLogEntry(long timestamp, String eventType, String materialId,
                               String materialTitle, String description) {
                this.timestamp = timestamp;
                this.eventType = eventType;
                this.materialId = materialId;
                this.materialTitle = materialTitle;
                this.description = description;
            }

            /** Gets the timestamp of the event. @return timestamp of the event */
            public long getTimestamp() { return timestamp; }
            /** Gets the type of event. @return type of event */
            public String getEventType() { return eventType; }
            /** Gets the ID of the material. @return ID of the material */
            public String getMaterialId() { return materialId; }
            /** Gets the title of the material. @return title of the material */
            public String getMaterialTitle() { return materialTitle; }
            /** Gets the event description. @return event description */
            public String getDescription() { return description; }

            @Override
            public String toString() {
                return String.format("[%s] %s: %s (ID: %s) - %s",
                    new Date(timestamp),
                    eventType,
                    materialTitle,
                    materialId,
                    description);
            }
        }

        /**
         * Statistics for the audit log.
         */
        public static class AuditLogStats {
            private final int totalEntries;
            private final int materialAddedCount;
            private final int priceChangedCount;
            private final long oldestTimestamp;
            private final long newestTimestamp;

            /**
             * Creates audit log statistics.
             *
             * @param totalEntries total number of entries
             * @param materialAddedCount number of material added events
             * @param priceChangedCount number of price changed events
             * @param oldestTimestamp timestamp of oldest entry
             * @param newestTimestamp timestamp of newest entry
             */
            public AuditLogStats(int totalEntries, int materialAddedCount, int priceChangedCount,
                               long oldestTimestamp, long newestTimestamp) {
                this.totalEntries = totalEntries;
                this.materialAddedCount = materialAddedCount;
                this.priceChangedCount = priceChangedCount;
                this.oldestTimestamp = oldestTimestamp;
                this.newestTimestamp = newestTimestamp;
            }

            /** Gets the total number of entries. @return total number of entries */
            public int getTotalEntries() { return totalEntries; }
            /** Gets the number of material added events. @return number of material added events */
            public int getMaterialAddedCount() { return materialAddedCount; }
            /** Gets the number of price changed events. @return number of price changed events */
            public int getPriceChangedCount() { return priceChangedCount; }
            /** Gets the timestamp of oldest entry. @return timestamp of oldest entry */
            public long getOldestTimestamp() { return oldestTimestamp; }
            /** Gets the timestamp of newest entry. @return timestamp of newest entry */
            public long getNewestTimestamp() { return newestTimestamp; }

            @Override
            public String toString() {
                return String.format("AuditLogStats[Total=%d, Added=%d, PriceChanged=%d, Oldest=%s, Newest=%s]",
                    totalEntries, materialAddedCount, priceChangedCount,
                    new Date(oldestTimestamp), new Date(newestTimestamp));
            }
        }

        @Override
        public String toString() {
            return String.format("AuditLogObserver[LogSize=%d, MaxSize=%d]", getLogSize(), maxLogSize);
        }
    }