package com.social.media.domain.analytics.valueobject;

import com.social.media.domain.shared.exception.BusinessRuleViolationException;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * Value Object representing a metric value with proper validation and calculations
 */
public record MetricValue(
    BigDecimal value,
    MetricType type
) {
    
    public MetricValue {
        if (value == null) {
            throw new BusinessRuleViolationException("Metric value cannot be null");
        }
        if (type == null) {
            throw new BusinessRuleViolationException("Metric type cannot be null");
        }
        
        // Validate value constraints based on type
        validateValueForType(value, type);
    }
    
    public static MetricValue of(long value, MetricType type) {
        return new MetricValue(BigDecimal.valueOf(value), type);
    }
    
    public static MetricValue of(double value, MetricType type) {
        return new MetricValue(BigDecimal.valueOf(value), type);
    }
    
    public static MetricValue of(BigDecimal value, MetricType type) {
        return new MetricValue(value, type);
    }
    
    public static MetricValue zero(MetricType type) {
        return new MetricValue(BigDecimal.ZERO, type);
    }
    
    public long asLong() {
        return value.longValue();
    }
    
    public double asDouble() {
        return value.doubleValue();
    }
    
    public BigDecimal asBigDecimal() {
        return value;
    }
    
    public boolean isZero() {
        return value.compareTo(BigDecimal.ZERO) == 0;
    }
    
    public boolean isPositive() {
        return value.compareTo(BigDecimal.ZERO) > 0;
    }
    
    public boolean isNegative() {
        return value.compareTo(BigDecimal.ZERO) < 0;
    }
    
    public MetricValue add(MetricValue other) {
        validateSameType(other);
        return new MetricValue(this.value.add(other.value), this.type);
    }
    
    public MetricValue subtract(MetricValue other) {
        validateSameType(other);
        return new MetricValue(this.value.subtract(other.value), this.type);
    }
    
    public MetricValue multiply(BigDecimal multiplier) {
        return new MetricValue(this.value.multiply(multiplier), this.type);
    }
    
    public MetricValue divide(BigDecimal divisor) {
        if (divisor.compareTo(BigDecimal.ZERO) == 0) {
            throw new BusinessRuleViolationException("Cannot divide by zero");
        }
        return new MetricValue(this.value.divide(divisor, 4, RoundingMode.HALF_UP), this.type);
    }
    
    public MetricValue percentage(MetricValue total) {
        if (total.isZero()) {
            return MetricValue.zero(MetricType.ENGAGEMENT_RATE);
        }
        validateSameType(total);
        BigDecimal percentage = this.value
            .multiply(BigDecimal.valueOf(100))
            .divide(total.value, 2, RoundingMode.HALF_UP);
        return new MetricValue(percentage, MetricType.ENGAGEMENT_RATE);
    }
    
    public int compareTo(MetricValue other) {
        validateSameType(other);
        return this.value.compareTo(other.value);
    }
    
    public boolean isGreaterThan(MetricValue other) {
        return compareTo(other) > 0;
    }
    
    public boolean isLessThan(MetricValue other) {
        return compareTo(other) < 0;
    }
    
    public boolean isEqualTo(MetricValue other) {
        return compareTo(other) == 0;
    }
    
    public MetricValue growth(MetricValue previousValue) {
        validateSameType(previousValue);
        if (previousValue.isZero()) {
            return this; // 100% growth if previous was zero
        }
        
        BigDecimal growth = this.value
            .subtract(previousValue.value)
            .divide(previousValue.value, 4, RoundingMode.HALF_UP)
            .multiply(BigDecimal.valueOf(100));
            
        return new MetricValue(growth, MetricType.ENGAGEMENT_RATE);
    }
    
    public String formatForDisplay() {
        if (type.isPercentageMetric()) {
            return String.format("%.2f%%", value.doubleValue());
        } else if (value.compareTo(BigDecimal.valueOf(1000)) >= 0) {
            // Format large numbers with K, M, B suffixes
            return formatLargeNumber();
        } else {
            return value.toPlainString();
        }
    }
    
    private String formatLargeNumber() {
        double val = value.doubleValue();
        if (val >= 1_000_000_000) {
            return String.format("%.1fB", val / 1_000_000_000);
        } else if (val >= 1_000_000) {
            return String.format("%.1fM", val / 1_000_000);
        } else if (val >= 1_000) {
            return String.format("%.1fK", val / 1_000);
        } else {
            return value.toPlainString();
        }
    }
    
    private void validateValueForType(BigDecimal value, MetricType type) {
        if (type.isCountMetric() && value.compareTo(BigDecimal.ZERO) < 0) {
            throw new BusinessRuleViolationException("Count metrics cannot be negative: " + type);
        }
        
        if (type.isPercentageMetric()) {
            if (value.compareTo(BigDecimal.ZERO) < 0 || value.compareTo(BigDecimal.valueOf(100)) > 0) {
                throw new BusinessRuleViolationException("Percentage metrics must be between 0 and 100: " + type);
            }
        }
    }
    
    private void validateSameType(MetricValue other) {
        if (!this.type.equals(other.type)) {
            throw new BusinessRuleViolationException(
                "Cannot perform operation on different metric types: " + this.type + " and " + other.type
            );
        }
    }
    
    @Override
    public String toString() {
        return formatForDisplay();
    }
}
