package com.social.media.domain.socialaccount;

import com.social.media.domain.shared.BaseValueObject;

import java.time.LocalDateTime;
import java.util.Objects;

/**
 * Value object representing the connection status of a social media account.
 * Tracks the health and connectivity state of the account integration.
 */
public final class ConnectionStatus extends BaseValueObject {
    
    public enum Status {
        CONNECTED("Connected", "Account is successfully connected and operational"),
        DISCONNECTED("Disconnected", "Account is not connected"),
        EXPIRED("Expired", "Authentication has expired and needs renewal"),
        SUSPENDED("Suspended", "Account access has been suspended"),
        RATE_LIMITED("Rate Limited", "Account is temporarily rate limited"),
        ERROR("Error", "Connection is in an error state"),
        MAINTENANCE("Maintenance", "Platform is under maintenance"),
        INVALID("Invalid", "Account credentials are invalid"),
        PENDING("Pending", "Connection is being established"),
        RECONNECTING("Reconnecting", "Attempting to reconnect");
        
        private final String displayName;
        private final String description;
        
        Status(String displayName, String description) {
            this.displayName = displayName;
            this.description = description;
        }
        
        public String getDisplayName() { return displayName; }
        public String getDescription() { return description; }
        
        public boolean isOperational() {
            return this == CONNECTED;
        }
        
        public boolean requiresUserAction() {
            return this == DISCONNECTED || this == EXPIRED || 
                   this == SUSPENDED || this == INVALID;
        }
        
        public boolean isTemporary() {
            return this == RATE_LIMITED || this == MAINTENANCE || 
                   this == PENDING || this == RECONNECTING;
        }
    }
    
    private final Status status;
    private final String message;
    private final String errorCode;
    private final LocalDateTime lastChecked;
    private final LocalDateTime lastSuccessfulConnection;
    private final Integer consecutiveFailures;
    private final LocalDateTime nextRetryAt;
    private final boolean autoRetryEnabled;
    
    public ConnectionStatus(
            Status status,
            String message,
            String errorCode,
            LocalDateTime lastChecked,
            LocalDateTime lastSuccessfulConnection,
            Integer consecutiveFailures,
            LocalDateTime nextRetryAt,
            boolean autoRetryEnabled) {
        
        this.status = validateStatus(status);
        this.message = message != null ? message.trim() : null;
        this.errorCode = errorCode != null ? errorCode.trim() : null;
        this.lastChecked = lastChecked != null ? lastChecked : LocalDateTime.now();
        this.lastSuccessfulConnection = lastSuccessfulConnection;
        this.consecutiveFailures = validateConsecutiveFailures(consecutiveFailures);
        this.nextRetryAt = nextRetryAt;
        this.autoRetryEnabled = autoRetryEnabled;
        
        validate();
    }
    
    private Status validateStatus(Status status) {
        if (status == null) {
            throw new IllegalArgumentException("Status cannot be null");
        }
        return status;
    }
    
    private Integer validateConsecutiveFailures(Integer consecutiveFailures) {
        if (consecutiveFailures != null && consecutiveFailures < 0) {
            throw new IllegalArgumentException("Consecutive failures cannot be negative");
        }
        return consecutiveFailures != null ? consecutiveFailures : 0;
    }
    
    @Override
    public void validate() {
        // Message should be provided for error states
        if (status == Status.ERROR && (message == null || message.isEmpty())) {
            throw new IllegalArgumentException("Error status must have a message");
        }
        
        // Error code should be provided for specific error states
        if ((status == Status.ERROR || status == Status.INVALID) && 
            (errorCode == null || errorCode.isEmpty())) {
            // This is a warning, not a hard requirement
        }
        
        // Next retry should be in the future if set
        if (nextRetryAt != null && nextRetryAt.isBefore(LocalDateTime.now().minusMinutes(1))) {
            throw new IllegalArgumentException("Next retry time should be in the future");
        }
        
        // Validate message length
        if (message != null && message.length() > 1000) {
            throw new IllegalArgumentException("Message cannot exceed 1000 characters");
        }
        
        // Validate error code format
        if (errorCode != null && errorCode.length() > 50) {
            throw new IllegalArgumentException("Error code cannot exceed 50 characters");
        }
    }
    
    /**
     * Creates a connected status.
     */
    public static ConnectionStatus connected() {
        return new ConnectionStatus(
            Status.CONNECTED,
            "Account is connected and operational",
            null,
            LocalDateTime.now(),
            LocalDateTime.now(),
            0,
            null,
            true
        );
    }
    
    /**
     * Creates a disconnected status.
     */
    public static ConnectionStatus disconnected(String reason) {
        return new ConnectionStatus(
            Status.DISCONNECTED,
            reason != null ? reason : "Account has been disconnected",
            null,
            LocalDateTime.now(),
            null,
            0,
            null,
            false
        );
    }
    
    /**
     * Creates an expired status.
     */
    public static ConnectionStatus expired() {
        return new ConnectionStatus(
            Status.EXPIRED,
            "Authentication has expired and needs to be renewed",
            "TOKEN_EXPIRED",
            LocalDateTime.now(),
            null,
            0,
            null,
            false
        );
    }
    
    /**
     * Creates an error status.
     */
    public static ConnectionStatus error(String message, String errorCode) {
        return new ConnectionStatus(
            Status.ERROR,
            message != null ? message : "Connection error occurred",
            errorCode,
            LocalDateTime.now(),
            null,
            1,
            LocalDateTime.now().plusMinutes(5), // Retry in 5 minutes
            true
        );
    }
    
    /**
     * Creates a rate limited status.
     */
    public static ConnectionStatus rateLimited(LocalDateTime retryAt) {
        return new ConnectionStatus(
            Status.RATE_LIMITED,
            "Account is temporarily rate limited",
            "RATE_LIMIT_EXCEEDED",
            LocalDateTime.now(),
            null,
            0,
            retryAt,
            true
        );
    }
    
    /**
     * Creates a suspended status.
     */
    public static ConnectionStatus suspended(String reason) {
        return new ConnectionStatus(
            Status.SUSPENDED,
            reason != null ? reason : "Account access has been suspended",
            "ACCOUNT_SUSPENDED",
            LocalDateTime.now(),
            null,
            0,
            null,
            false
        );
    }
    
    /**
     * Creates a maintenance status.
     */
    public static ConnectionStatus maintenance() {
        return new ConnectionStatus(
            Status.MAINTENANCE,
            "Platform is under maintenance",
            "MAINTENANCE_MODE",
            LocalDateTime.now(),
            null,
            0,
            LocalDateTime.now().plusMinutes(30), // Retry in 30 minutes
            true
        );
    }
    
    /**
     * Creates a pending status.
     */
    public static ConnectionStatus pending() {
        return new ConnectionStatus(
            Status.PENDING,
            "Connection is being established",
            null,
            LocalDateTime.now(),
            null,
            0,
            LocalDateTime.now().plusMinutes(2), // Check again in 2 minutes
            true
        );
    }
    
    /**
     * Updates the status with a successful connection.
     */
    public ConnectionStatus withSuccessfulConnection() {
        return new ConnectionStatus(
            Status.CONNECTED,
            "Account is connected and operational",
            null,
            LocalDateTime.now(),
            LocalDateTime.now(),
            0,
            null,
            autoRetryEnabled
        );
    }
    
    /**
     * Updates the status with a failure.
     */
    public ConnectionStatus withFailure(String message, String errorCode) {
        int newFailureCount = consecutiveFailures + 1;
        LocalDateTime nextRetry = calculateNextRetry(newFailureCount);
        
        return new ConnectionStatus(
            Status.ERROR,
            message != null ? message : "Connection failed",
            errorCode,
            LocalDateTime.now(),
            lastSuccessfulConnection,
            newFailureCount,
            nextRetry,
            autoRetryEnabled
        );
    }
    
    /**
     * Updates the status with a reconnection attempt.
     */
    public ConnectionStatus withReconnectionAttempt() {
        return new ConnectionStatus(
            Status.RECONNECTING,
            "Attempting to reconnect",
            null,
            LocalDateTime.now(),
            lastSuccessfulConnection,
            consecutiveFailures,
            LocalDateTime.now().plusMinutes(1), // Check in 1 minute
            autoRetryEnabled
        );
    }
    
    /**
     * Calculates the next retry time based on exponential backoff.
     */
    private LocalDateTime calculateNextRetry(int failureCount) {
        if (!autoRetryEnabled) {
            return null;
        }
        
        // Exponential backoff: 1min, 2min, 4min, 8min, 16min, max 30min
        int minutes = Math.min(30, (int) Math.pow(2, Math.min(failureCount - 1, 4)));
        return LocalDateTime.now().plusMinutes(minutes);
    }
    
    /**
     * Checks if the account is ready for use.
     */
    public boolean isReady() {
        return status.isOperational();
    }
    
    /**
     * Checks if the status requires user intervention.
     */
    public boolean requiresUserAction() {
        return status.requiresUserAction();
    }
    
    /**
     * Checks if the status is temporary.
     */
    public boolean isTemporary() {
        return status.isTemporary();
    }
    
    /**
     * Checks if a retry should be attempted.
     */
    public boolean shouldRetry() {
        return autoRetryEnabled && 
               nextRetryAt != null && 
               nextRetryAt.isBefore(LocalDateTime.now()) &&
               consecutiveFailures < 10; // Max 10 consecutive failures
    }
    
    /**
     * Gets the time until the next retry.
     */
    public Long getMinutesUntilNextRetry() {
        if (nextRetryAt == null) {
            return null;
        }
        
        long minutes = java.time.Duration.between(LocalDateTime.now(), nextRetryAt).toMinutes();
        return Math.max(0, minutes);
    }
    
    /**
     * Checks if the account has been failing for too long.
     */
    public boolean isHealthy() {
        return status.isOperational() || 
               status.isTemporary() || 
               consecutiveFailures < 5;
    }
    
    // Getters
    public Status getStatus() { return status; }
    public String getMessage() { return message; }
    public String getErrorCode() { return errorCode; }
    public LocalDateTime getLastChecked() { return lastChecked; }
    public LocalDateTime getLastSuccessfulConnection() { return lastSuccessfulConnection; }
    public Integer getConsecutiveFailures() { return consecutiveFailures; }
    public LocalDateTime getNextRetryAt() { return nextRetryAt; }
    public boolean isAutoRetryEnabled() { return autoRetryEnabled; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ConnectionStatus that = (ConnectionStatus) o;
        return autoRetryEnabled == that.autoRetryEnabled &&
               status == that.status &&
               Objects.equals(message, that.message) &&
               Objects.equals(errorCode, that.errorCode) &&
               Objects.equals(lastChecked, that.lastChecked) &&
               Objects.equals(lastSuccessfulConnection, that.lastSuccessfulConnection) &&
               Objects.equals(consecutiveFailures, that.consecutiveFailures) &&
               Objects.equals(nextRetryAt, that.nextRetryAt);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(status, message, errorCode, lastChecked,
                          lastSuccessfulConnection, consecutiveFailures,
                          nextRetryAt, autoRetryEnabled);
    }
    
    @Override
    public String toString() {
        return "ConnectionStatus{" +
               "status=" + status +
               ", message='" + message + '\'' +
               ", errorCode='" + errorCode + '\'' +
               ", lastChecked=" + lastChecked +
               ", lastSuccessfulConnection=" + lastSuccessfulConnection +
               ", consecutiveFailures=" + consecutiveFailures +
               ", nextRetryAt=" + nextRetryAt +
               ", autoRetryEnabled=" + autoRetryEnabled +
               '}';
    }
}
