package com.social.media.domain.socialaccount;

import com.social.media.domain.shared.BaseValueObject;

import java.time.LocalDateTime;
import java.util.Objects;

/**
 * Value object representing authentication tokens for a social media account.
 * Handles access tokens, refresh tokens, and their expiration.
 */
public final class AuthTokens extends BaseValueObject {
    
    private final String accessToken;
    private final String refreshToken;
    private final String tokenType;
    private final LocalDateTime expiresAt;
    private final LocalDateTime refreshExpiresAt;
    private final String scope;
    private final boolean isLongLived;
    
    public AuthTokens(
            String accessToken,
            String refreshToken,
            String tokenType,
            LocalDateTime expiresAt,
            LocalDateTime refreshExpiresAt,
            String scope,
            boolean isLongLived) {
        
        this.accessToken = validateAccessToken(accessToken);
        this.refreshToken = refreshToken; // Can be null for some platforms
        this.tokenType = validateTokenType(tokenType);
        this.expiresAt = expiresAt; // Can be null for non-expiring tokens
        this.refreshExpiresAt = refreshExpiresAt; // Can be null
        this.scope = scope; // Can be null
        this.isLongLived = isLongLived;
        
        validate();
    }
    
    private String validateAccessToken(String accessToken) {
        if (accessToken == null || accessToken.trim().isEmpty()) {
            throw new IllegalArgumentException("Access token cannot be null or empty");
        }
        if (accessToken.length() > 2000) {
            throw new IllegalArgumentException("Access token cannot exceed 2000 characters");
        }
        return accessToken.trim();
    }
    
    private String validateTokenType(String tokenType) {
        if (tokenType == null || tokenType.trim().isEmpty()) {
            return "Bearer"; // Default token type
        }
        return tokenType.trim();
    }
    
    @Override
    public void validate() {
        // If expiration is set, it should be in the future (allowing for clock skew)
        if (expiresAt != null && expiresAt.isBefore(LocalDateTime.now().minusMinutes(5))) {
            throw new IllegalArgumentException("Token expiration cannot be in the past");
        }
        
        // If refresh token expiration is set, it should be after access token expiration
        if (refreshExpiresAt != null && expiresAt != null && 
            refreshExpiresAt.isBefore(expiresAt)) {
            throw new IllegalArgumentException("Refresh token expiration should be after access token expiration");
        }
        
        // Long-lived tokens should have longer expiration times
        if (isLongLived && expiresAt != null && 
            expiresAt.isBefore(LocalDateTime.now().plusDays(30))) {
            throw new IllegalArgumentException("Long-lived tokens should expire after at least 30 days");
        }
    }
    
    /**
     * Creates a short-lived token (typically 1-2 hours).
     */
    public static AuthTokens createShortLived(
            String accessToken,
            String refreshToken,
            String scope,
            int expiresInSeconds) {
        
        LocalDateTime expiresAt = LocalDateTime.now().plusSeconds(expiresInSeconds);
        LocalDateTime refreshExpiresAt = refreshToken != null ? 
            LocalDateTime.now().plusDays(60) : null; // Refresh tokens typically last 60 days
        
        return new AuthTokens(
            accessToken,
            refreshToken,
            "Bearer",
            expiresAt,
            refreshExpiresAt,
            scope,
            false
        );
    }
    
    /**
     * Creates a long-lived token (typically 60 days or more).
     */
    public static AuthTokens createLongLived(
            String accessToken,
            String scope,
            int expiresInSeconds) {
        
        LocalDateTime expiresAt = expiresInSeconds > 0 ? 
            LocalDateTime.now().plusSeconds(expiresInSeconds) : null;
        
        return new AuthTokens(
            accessToken,
            null, // Long-lived tokens typically don't have refresh tokens
            "Bearer",
            expiresAt,
            null,
            scope,
            true
        );
    }
    
    /**
     * Creates a non-expiring token.
     */
    public static AuthTokens createNonExpiring(
            String accessToken,
            String scope) {
        
        return new AuthTokens(
            accessToken,
            null,
            "Bearer",
            null, // Never expires
            null,
            scope,
            true
        );
    }
    
    /**
     * Checks if the access token is expired.
     */
    public boolean isExpired() {
        return expiresAt != null && expiresAt.isBefore(LocalDateTime.now());
    }
    
    /**
     * Checks if the access token will expire within the specified minutes.
     */
    public boolean willExpireWithin(int minutes) {
        return expiresAt != null && expiresAt.isBefore(LocalDateTime.now().plusMinutes(minutes));
    }
    
    /**
     * Checks if the refresh token is expired.
     */
    public boolean isRefreshTokenExpired() {
        return refreshExpiresAt != null && refreshExpiresAt.isBefore(LocalDateTime.now());
    }
    
    /**
     * Checks if the token can be refreshed.
     */
    public boolean canBeRefreshed() {
        return refreshToken != null && !isRefreshTokenExpired();
    }
    
    /**
     * Gets the remaining time in seconds until the token expires.
     */
    public Long getSecondsUntilExpiration() {
        if (expiresAt == null) {
            return null; // Never expires
        }
        
        long seconds = java.time.Duration.between(LocalDateTime.now(), expiresAt).getSeconds();
        return Math.max(0, seconds);
    }
    
    /**
     * Creates a new AuthTokens instance with updated access token.
     */
    public AuthTokens withNewAccessToken(String newAccessToken, int expiresInSeconds) {
        LocalDateTime newExpiresAt = expiresInSeconds > 0 ? 
            LocalDateTime.now().plusSeconds(expiresInSeconds) : expiresAt;
        
        return new AuthTokens(
            newAccessToken,
            refreshToken,
            tokenType,
            newExpiresAt,
            refreshExpiresAt,
            scope,
            isLongLived
        );
    }
    
    /**
     * Creates a new AuthTokens instance with updated refresh token.
     */
    public AuthTokens withNewRefreshToken(String newRefreshToken, int refreshExpiresInSeconds) {
        LocalDateTime newRefreshExpiresAt = refreshExpiresInSeconds > 0 ? 
            LocalDateTime.now().plusSeconds(refreshExpiresInSeconds) : refreshExpiresAt;
        
        return new AuthTokens(
            accessToken,
            newRefreshToken,
            tokenType,
            expiresAt,
            newRefreshExpiresAt,
            scope,
            isLongLived
        );
    }
    
    /**
     * Creates authorization header value.
     */
    public String getAuthorizationHeader() {
        return tokenType + " " + accessToken;
    }
    
    /**
     * Checks if the token has a specific scope.
     */
    public boolean hasScope(String requiredScope) {
        if (scope == null || requiredScope == null) {
            return false;
        }
        
        String[] scopes = scope.split("[,\\s]+");
        for (String s : scopes) {
            if (s.trim().equalsIgnoreCase(requiredScope.trim())) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Checks if the token has all required scopes.
     */
    public boolean hasAllScopes(String... requiredScopes) {
        if (requiredScopes == null || requiredScopes.length == 0) {
            return true;
        }
        
        for (String requiredScope : requiredScopes) {
            if (!hasScope(requiredScope)) {
                return false;
            }
        }
        return true;
    }
    
    /**
     * Gets a masked version of the access token for logging.
     */
    public String getMaskedAccessToken() {
        if (accessToken.length() <= 8) {
            return "***";
        }
        return accessToken.substring(0, 4) + "..." + 
               accessToken.substring(accessToken.length() - 4);
    }
    
    // Getters
    public String getAccessToken() { return accessToken; }
    public String getRefreshToken() { return refreshToken; }
    public String getTokenType() { return tokenType; }
    public LocalDateTime getExpiresAt() { return expiresAt; }
    public LocalDateTime getRefreshExpiresAt() { return refreshExpiresAt; }
    public String getScope() { return scope; }
    public boolean isLongLived() { return isLongLived; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AuthTokens that = (AuthTokens) o;
        return isLongLived == that.isLongLived &&
               Objects.equals(accessToken, that.accessToken) &&
               Objects.equals(refreshToken, that.refreshToken) &&
               Objects.equals(tokenType, that.tokenType) &&
               Objects.equals(expiresAt, that.expiresAt) &&
               Objects.equals(refreshExpiresAt, that.refreshExpiresAt) &&
               Objects.equals(scope, that.scope);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(accessToken, refreshToken, tokenType, 
                          expiresAt, refreshExpiresAt, scope, isLongLived);
    }
    
    @Override
    public String toString() {
        return "AuthTokens{" +
               "accessToken='" + getMaskedAccessToken() + '\'' +
               ", hasRefreshToken=" + (refreshToken != null) +
               ", tokenType='" + tokenType + '\'' +
               ", expiresAt=" + expiresAt +
               ", refreshExpiresAt=" + refreshExpiresAt +
               ", scope='" + scope + '\'' +
               ", isLongLived=" + isLongLived +
               ", isExpired=" + isExpired() +
               '}';
    }
}
