package com.social.media.domain.analytics.aggregate;

import com.social.media.domain.analytics.valueobject.*;
import com.social.media.domain.company.valueobject.CompanyId;
import com.social.media.domain.socialaccount.valueobject.SocialAccountId;
import com.social.media.domain.shared.exception.BusinessRuleViolationException;
import com.social.media.domain.shared.kernel.AggregateRoot;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.HashMap;
import java.util.EnumMap;
import java.math.BigDecimal;

/**
 * AccountMetrics aggregate representing social account performance metrics
 */
public class AccountMetrics extends AggregateRoot<AccountMetricsId> {
    
    private CompanyId companyId;
    private SocialAccountId socialAccountId;
    private TimePeriod period;
    private LocalDateTime periodStart;
    private LocalDateTime periodEnd;
    private Map<MetricType, MetricValue> metrics;
    private LocalDateTime collectedAt;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // Constructor for new metrics
    public AccountMetrics(CompanyId companyId, SocialAccountId socialAccountId, 
                         TimePeriod period, LocalDateTime periodStart, LocalDateTime periodEnd) {
        super(AccountMetricsId.generate());
        this.companyId = validateCompanyId(companyId);
        this.socialAccountId = validateSocialAccountId(socialAccountId);
        this.period = validatePeriod(period);
        this.periodStart = validatePeriodStart(periodStart);
        this.periodEnd = validatePeriodEnd(periodEnd, periodStart);
        this.metrics = new EnumMap<>(MetricType.class);
        this.collectedAt = LocalDateTime.now();
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
        
        // Initialize with zero values for all metric types
        initializeMetrics();
    }
    
    // Constructor for existing metrics
    public AccountMetrics(AccountMetricsId id, CompanyId companyId, SocialAccountId socialAccountId,
                         TimePeriod period, LocalDateTime periodStart, LocalDateTime periodEnd,
                         Map<MetricType, MetricValue> metrics, LocalDateTime collectedAt,
                         LocalDateTime createdAt, LocalDateTime updatedAt) {
        super(id);
        this.companyId = companyId;
        this.socialAccountId = socialAccountId;
        this.period = period;
        this.periodStart = periodStart;
        this.periodEnd = periodEnd;
        this.metrics = metrics != null ? new EnumMap<>(metrics) : new EnumMap<>(MetricType.class);
        this.collectedAt = collectedAt;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }
    
    // Business methods
    public void updateMetric(MetricType type, MetricValue value) {
        if (type == null) {
            throw new BusinessRuleViolationException("Metric type cannot be null");
        }
        if (value == null) {
            throw new BusinessRuleViolationException("Metric value cannot be null");
        }
        if (!type.equals(value.type())) {
            throw new BusinessRuleViolationException("Metric type mismatch");
        }
        
        this.metrics.put(type, value);
        this.updatedAt = LocalDateTime.now();
    }
    
    public void updateMetric(MetricType type, long value) {
        updateMetric(type, MetricValue.of(value, type));
    }
    
    public void updateMetric(MetricType type, double value) {
        updateMetric(type, MetricValue.of(value, type));
    }
    
    public void updateMetric(MetricType type, BigDecimal value) {
        updateMetric(type, MetricValue.of(value, type));
    }
    
    public void incrementMetric(MetricType type, long increment) {
        MetricValue current = getMetric(type);
        MetricValue newValue = current.add(MetricValue.of(increment, type));
        updateMetric(type, newValue);
    }
    
    public void updateMultipleMetrics(Map<MetricType, MetricValue> metricsUpdate) {
        if (metricsUpdate == null || metricsUpdate.isEmpty()) {
            return;
        }
        
        for (Map.Entry<MetricType, MetricValue> entry : metricsUpdate.entrySet()) {
            updateMetric(entry.getKey(), entry.getValue());
        }
    }
    
    public void markAsCollected() {
        this.collectedAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    public void calculateEngagementRate() {
        MetricValue followers = getMetric(MetricType.FOLLOWERS);
        MetricValue likes = getMetric(MetricType.LIKES);
        MetricValue comments = getMetric(MetricType.COMMENTS);
        MetricValue shares = getMetric(MetricType.SHARES);
        
        if (followers.isZero()) {
            updateMetric(MetricType.ENGAGEMENT_RATE, 0.0);
            return;
        }
        
        MetricValue totalEngagement = likes.add(comments).add(shares);
        MetricValue engagementRate = totalEngagement.percentage(followers);
        updateMetric(MetricType.ENGAGEMENT_RATE, engagementRate);
    }
    
    public void calculateGrowthMetrics(AccountMetrics previousPeriod) {
        if (previousPeriod == null) {
            return;
        }
        
        // Growth metrics calculation would be implemented here
        // Could be stored in a separate analytics entity or as additional metric types
        // For now, this method serves as a placeholder for future growth calculations
    }
    
    // Query methods
    public MetricValue getMetric(MetricType type) {
        return metrics.getOrDefault(type, MetricValue.zero(type));
    }
    
    public boolean hasMetric(MetricType type) {
        return metrics.containsKey(type) && !getMetric(type).isZero();
    }
    
    public Map<MetricType, MetricValue> getEngagementMetrics() {
        Map<MetricType, MetricValue> engagementMetrics = new HashMap<>();
        for (MetricType type : MetricType.values()) {
            if (type.isEngagementMetric() && hasMetric(type)) {
                engagementMetrics.put(type, getMetric(type));
            }
        }
        return engagementMetrics;
    }
    
    public Map<MetricType, MetricValue> getReachMetrics() {
        Map<MetricType, MetricValue> reachMetrics = new HashMap<>();
        for (MetricType type : MetricType.values()) {
            if (type.isReachMetric() && hasMetric(type)) {
                reachMetrics.put(type, getMetric(type));
            }
        }
        return reachMetrics;
    }
    
    public Map<MetricType, MetricValue> getFollowerMetrics() {
        Map<MetricType, MetricValue> followerMetrics = new HashMap<>();
        for (MetricType type : MetricType.values()) {
            if (type.isFollowerMetric() && hasMetric(type)) {
                followerMetrics.put(type, getMetric(type));
            }
        }
        return followerMetrics;
    }
    
    public boolean belongsToCompany(CompanyId companyId) {
        return this.companyId.equals(companyId);
    }
    
    public boolean belongsToSocialAccount(SocialAccountId socialAccountId) {
        return this.socialAccountId.equals(socialAccountId);
    }
    
    public boolean isPeriodComplete() {
        return LocalDateTime.now().isAfter(this.periodEnd);
    }
    
    public boolean isCurrentPeriod() {
        LocalDateTime now = LocalDateTime.now();
        return !now.isBefore(this.periodStart) && !now.isAfter(this.periodEnd);
    }
    
    public boolean isRecentlyCollected(int hours) {
        if (this.collectedAt == null) {
            return false;
        }
        return this.collectedAt.isAfter(LocalDateTime.now().minusHours(hours));
    }
    
    public long getTotalEngagement() {
        MetricValue likes = getMetric(MetricType.LIKES);
        MetricValue comments = getMetric(MetricType.COMMENTS);
        MetricValue shares = getMetric(MetricType.SHARES);
        MetricValue saves = getMetric(MetricType.SAVES);
        
        return likes.asLong() + comments.asLong() + shares.asLong() + saves.asLong();
    }
    
    public double getEngagementRateValue() {
        return getMetric(MetricType.ENGAGEMENT_RATE).asDouble();
    }
    
    // Private methods
    private void initializeMetrics() {
        for (MetricType type : MetricType.values()) {
            this.metrics.put(type, MetricValue.zero(type));
        }
    }
    
    // Validation methods
    private CompanyId validateCompanyId(CompanyId companyId) {
        if (companyId == null) {
            throw new BusinessRuleViolationException("Company ID is required");
        }
        return companyId;
    }
    
    private SocialAccountId validateSocialAccountId(SocialAccountId socialAccountId) {
        if (socialAccountId == null) {
            throw new BusinessRuleViolationException("Social account ID is required");
        }
        return socialAccountId;
    }
    
    private TimePeriod validatePeriod(TimePeriod period) {
        if (period == null) {
            throw new BusinessRuleViolationException("Time period is required");
        }
        return period;
    }
    
    private LocalDateTime validatePeriodStart(LocalDateTime periodStart) {
        if (periodStart == null) {
            throw new BusinessRuleViolationException("Period start is required");
        }
        return periodStart;
    }
    
    private LocalDateTime validatePeriodEnd(LocalDateTime periodEnd, LocalDateTime periodStart) {
        if (periodEnd == null) {
            throw new BusinessRuleViolationException("Period end is required");
        }
        if (!periodEnd.isAfter(periodStart)) {
            throw new BusinessRuleViolationException("Period end must be after period start");
        }
        return periodEnd;
    }
    
    // Getters
    public CompanyId getCompanyId() { return companyId; }
    public SocialAccountId getSocialAccountId() { return socialAccountId; }
    public TimePeriod getPeriod() { return period; }
    public LocalDateTime getPeriodStart() { return periodStart; }
    public LocalDateTime getPeriodEnd() { return periodEnd; }
    public Map<MetricType, MetricValue> getMetrics() { return new EnumMap<>(metrics); }
    public LocalDateTime getCollectedAt() { return collectedAt; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public LocalDateTime getUpdatedAt() { return updatedAt; }
}
