package com.social.media.domain.shared;

import com.social.media.domain.shared.valueobjects.EntityId;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Base class for all Aggregate Roots in the domain
 * 
 * Provides common functionality like:
 * - Entity identification
 * - Auditing (created/modified dates)
 * - Domain events management
 * - Basic entity behavior
 * 
 * @param <ID> the type of the entity identifier
 * 
 * @author Social Media Manager Team
 * @since 2.0.0
 */
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public abstract class AggregateRoot<ID extends EntityId> {
    
    /**
     * The unique identifier for this aggregate
     */
    @Id
    @Column(name = "id")
    protected ID id;
    
    /**
     * When this aggregate was created
     */
    @CreatedDate
    @Column(name = "created_at", nullable = false, updatable = false)
    protected LocalDateTime createdAt;
    
    /**
     * When this aggregate was last modified
     */
    @LastModifiedDate
    @Column(name = "updated_at", nullable = false)
    protected LocalDateTime updatedAt;
    
    /**
     * Version for optimistic locking
     */
    @Version
    @Column(name = "version")
    protected Long version;
    
    /**
     * Domain events raised by this aggregate
     */
    @Transient
    @Getter(AccessLevel.NONE)
    private final List<DomainEvent> domainEvents = new ArrayList<>();
    
    /**
     * Constructor for creating aggregate with specific ID
     */
    protected AggregateRoot(ID id) {
        this.id = Objects.requireNonNull(id, "Aggregate ID cannot be null");
    }
    
    /**
     * Get the aggregate ID
     */
    public ID getId() {
        return id;
    }
    
    /**
     * Set the aggregate ID (protected access for subclasses)
     */
    protected void setId(ID id) {
        this.id = id;
    }
    
    /**
     * Get all domain events raised by this aggregate
     */
    public List<DomainEvent> getDomainEvents() {
        return Collections.unmodifiableList(domainEvents);
    }
    
    /**
     * Clear all domain events
     */
    public void clearDomainEvents() {
        domainEvents.clear();
    }
    
    /**
     * Raise a domain event
     */
    protected void raiseEvent(DomainEvent event) {
        if (event != null) {
            domainEvents.add(event);
        }
    }
    
    /**
     * Check if this aggregate has uncommitted domain events
     */
    public boolean hasDomainEvents() {
        return !domainEvents.isEmpty();
    }
    
    /**
     * Check if this aggregate is new (not persisted yet)
     */
    public boolean isNew() {
        return version == null || version == 0;
    }
    
    /**
     * Called before persisting the aggregate
     */
    @PrePersist
    protected void prePersist() {
        if (createdAt == null) {
            createdAt = LocalDateTime.now();
        }
        if (updatedAt == null) {
            updatedAt = LocalDateTime.now();
        }
    }
    
    /**
     * Called before updating the aggregate
     */
    @PreUpdate
    protected void preUpdate() {
        updatedAt = LocalDateTime.now();
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        AggregateRoot<?> that = (AggregateRoot<?>) obj;
        return Objects.equals(id, that.id);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
    
    @Override
    public String toString() {
        return String.format("%s{id=%s, version=%s}", 
            getClass().getSimpleName(), id, version);
    }
}
