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.content.valueobject.PostId;
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;

/**
 * PostMetrics aggregate representing individual post performance metrics
 */
public class PostMetrics extends AggregateRoot<PostMetricsId> {
    
    private CompanyId companyId;
    private PostId postId;
    private SocialAccountId socialAccountId;
    private Map<MetricType, MetricValue> metrics;
    private LocalDateTime firstCollectedAt;
    private LocalDateTime lastCollectedAt;
    private int collectionCount;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // Constructor for new post metrics
    public PostMetrics(CompanyId companyId, PostId postId, SocialAccountId socialAccountId) {
        super(PostMetricsId.generate());
        this.companyId = validateCompanyId(companyId);
        this.postId = validatePostId(postId);
        this.socialAccountId = validateSocialAccountId(socialAccountId);
        this.metrics = new EnumMap<>(MetricType.class);
        this.firstCollectedAt = LocalDateTime.now();
        this.lastCollectedAt = LocalDateTime.now();
        this.collectionCount = 0;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
        
        // Initialize with zero values for post-relevant metrics
        initializePostMetrics();
    }
    
    // Constructor for existing post metrics
    public PostMetrics(PostMetricsId id, CompanyId companyId, PostId postId, SocialAccountId socialAccountId,
                      Map<MetricType, MetricValue> metrics, LocalDateTime firstCollectedAt,
                      LocalDateTime lastCollectedAt, int collectionCount,
                      LocalDateTime createdAt, LocalDateTime updatedAt) {
        super(id);
        this.companyId = companyId;
        this.postId = postId;
        this.socialAccountId = socialAccountId;
        this.metrics = metrics != null ? new EnumMap<>(metrics) : new EnumMap<>(MetricType.class);
        this.firstCollectedAt = firstCollectedAt;
        this.lastCollectedAt = lastCollectedAt;
        this.collectionCount = collectionCount;
        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");
        }
        if (!isPostRelevantMetric(type)) {
            throw new BusinessRuleViolationException("Metric type not applicable to posts: " + type);
        }
        
        this.metrics.put(type, value);
        this.lastCollectedAt = LocalDateTime.now();
        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) {
        if (!isPostRelevantMetric(type)) {
            throw new BusinessRuleViolationException("Metric type not applicable to posts: " + type);
        }
        
        MetricValue current = getMetric(type);
        MetricValue newValue = current.add(MetricValue.of(increment, type));
        this.metrics.put(type, newValue);
        this.lastCollectedAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    public void updateMetricsSnapshot(Map<MetricType, MetricValue> metricsSnapshot) {
        if (metricsSnapshot == null || metricsSnapshot.isEmpty()) {
            return;
        }
        
        for (Map.Entry<MetricType, MetricValue> entry : metricsSnapshot.entrySet()) {
            if (isPostRelevantMetric(entry.getKey())) {
                this.metrics.put(entry.getKey(), entry.getValue());
            }
        }
        
        this.collectionCount++;
        this.lastCollectedAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    public void calculateEngagementRate() {
        MetricValue views = getMetric(MetricType.VIEWS);
        MetricValue likes = getMetric(MetricType.LIKES);
        MetricValue comments = getMetric(MetricType.COMMENTS);
        MetricValue shares = getMetric(MetricType.SHARES);
        MetricValue saves = getMetric(MetricType.SAVES);
        
        if (views.isZero()) {
            updateMetric(MetricType.ENGAGEMENT_RATE, 0.0);
            return;
        }
        
        MetricValue totalEngagement = likes.add(comments).add(shares).add(saves);
        MetricValue engagementRate = totalEngagement.percentage(views);
        updateMetric(MetricType.ENGAGEMENT_RATE, engagementRate);
    }
    
    public void calculateReachRate() {
        MetricValue reach = getMetric(MetricType.REACH);
        
        if (reach.isZero()) {
            return; // Cannot calculate reach rate without reach data
        }
        
        // Reach rate calculation would be implemented here
        // Could store this as a custom metric or metadata in future iterations
    }
    
    // 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> getAllMetrics() {
        return new EnumMap<>(metrics);
    }
    
    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 boolean belongsToCompany(CompanyId companyId) {
        return this.companyId.equals(companyId);
    }
    
    public boolean belongsToPost(PostId postId) {
        return this.postId.equals(postId);
    }
    
    public boolean belongsToSocialAccount(SocialAccountId socialAccountId) {
        return this.socialAccountId.equals(socialAccountId);
    }
    
    public boolean isRecentlyUpdated(int hours) {
        if (this.lastCollectedAt == null) {
            return false;
        }
        return this.lastCollectedAt.isAfter(LocalDateTime.now().minusHours(hours));
    }
    
    public boolean hasSignificantEngagement() {
        long totalEngagement = getTotalEngagement();
        long views = getMetric(MetricType.VIEWS).asLong();
        
        if (views == 0) {
            return totalEngagement > 0;
        }
        
        // Consider significant if engagement rate > 2%
        return (double) totalEngagement / views > 0.02;
    }
    
    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();
    }
    
    public boolean isViralPost() {
        MetricValue shares = getMetric(MetricType.SHARES);
        MetricValue views = getMetric(MetricType.VIEWS);
        
        if (views.asLong() < 1000) {
            return false; // Needs minimum reach to be considered viral
        }
        
        // Consider viral if share rate > 5% and significant reach
        return shares.asLong() > 0 && 
               (double) shares.asLong() / views.asLong() > 0.05 &&
               views.asLong() > 10000;
    }
    
    public boolean isHighPerforming() {
        double engagementRate = getEngagementRateValue();
        long views = getMetric(MetricType.VIEWS).asLong();
        
        // High performing: good engagement rate AND decent reach
        return engagementRate > 3.0 && views > 500;
    }
    
    // Private methods
    private void initializePostMetrics() {
        // Initialize only post-relevant metrics
        MetricType[] postMetrics = {
            MetricType.LIKES, MetricType.COMMENTS, MetricType.SHARES,
            MetricType.VIEWS, MetricType.REACH, MetricType.SAVES,
            MetricType.ENGAGEMENT_RATE, MetricType.CLICK_THROUGH_RATE
        };
        
        for (MetricType type : postMetrics) {
            this.metrics.put(type, MetricValue.zero(type));
        }
    }
    
    private boolean isPostRelevantMetric(MetricType type) {
        return type == MetricType.LIKES || type == MetricType.COMMENTS || 
               type == MetricType.SHARES || type == MetricType.VIEWS ||
               type == MetricType.REACH || type == MetricType.SAVES ||
               type == MetricType.ENGAGEMENT_RATE || type == MetricType.CLICK_THROUGH_RATE ||
               type == MetricType.REEL_PLAYS;
    }
    
    // Validation methods
    private CompanyId validateCompanyId(CompanyId companyId) {
        if (companyId == null) {
            throw new BusinessRuleViolationException("Company ID is required");
        }
        return companyId;
    }
    
    private PostId validatePostId(PostId postId) {
        if (postId == null) {
            throw new BusinessRuleViolationException("Post ID is required");
        }
        return postId;
    }
    
    private SocialAccountId validateSocialAccountId(SocialAccountId socialAccountId) {
        if (socialAccountId == null) {
            throw new BusinessRuleViolationException("Social account ID is required");
        }
        return socialAccountId;
    }
    
    // Getters
    public CompanyId getCompanyId() { return companyId; }
    public PostId getPostId() { return postId; }
    public SocialAccountId getSocialAccountId() { return socialAccountId; }
    public Map<MetricType, MetricValue> getMetrics() { return new EnumMap<>(metrics); }
    public LocalDateTime getFirstCollectedAt() { return firstCollectedAt; }
    public LocalDateTime getLastCollectedAt() { return lastCollectedAt; }
    public int getCollectionCount() { return collectionCount; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public LocalDateTime getUpdatedAt() { return updatedAt; }
}
