package com.social.media.domain.entity;

import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import org.hibernate.annotations.DynamicUpdate;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

/**
 * Entity representing user credentials and security information
 * Maps to the security.user_credentials table
 */
@Entity
@Table(name = "user_credentials", schema = "security")
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
public class UserCredentials {
    
    @Id
    @Column(name = "user_id", nullable = false)
    private Long userId;
    
    @Column(name = "password_hash", nullable = false, length = 255)
    private String passwordHash;
    
    @Column(name = "failed_login_attempts", nullable = false, columnDefinition = "integer default 0")
    private Integer failedLoginAttempts = 0;
    
    @Column(name = "locked_until")
    private LocalDateTime lockedUntil;
    
    @Column(name = "last_login_at")
    private LocalDateTime lastLoginAt;
    
    @Column(name = "password_reset_token", length = 255)
    private String passwordResetToken;
    
    @Column(name = "password_reset_expires_at")
    private LocalDateTime passwordResetExpiresAt;
    
    @Column(name = "email_verification_token", length = 255)
    private String emailVerificationToken;
    
    @Column(name = "password_changed_at")
    private LocalDateTime passwordChangedAt;
    
    @Column(name = "two_factor_enabled", nullable = false, columnDefinition = "boolean default false")
    private Boolean twoFactorEnabled = false;
    
    @CreatedDate
    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;
    
    // One-to-one relationship with User entity
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    @MapsId
    private User user;
    
    // Business logic methods
    
    /**
     * Check if the account is currently locked
     */
    public boolean isAccountLocked() {
        return lockedUntil != null && lockedUntil.isAfter(LocalDateTime.now());
    }
    
    /**
     * Check if the account is permanently locked (no unlock time set)
     */
    public boolean isPermanentlyLocked() {
        return failedLoginAttempts != null && failedLoginAttempts >= getMaxFailedAttempts() && lockedUntil == null;
    }
    
    /**
     * Check if the password reset token is valid and not expired
     */
    public boolean isPasswordResetTokenValid() {
        return passwordResetToken != null && 
               passwordResetExpiresAt != null && 
               passwordResetExpiresAt.isAfter(LocalDateTime.now());
    }
    
    /**
     * Check if the email verification token is present
     */
    public boolean hasEmailVerificationToken() {
        return emailVerificationToken != null && !emailVerificationToken.trim().isEmpty();
    }
    
    /**
     * Check if the password was recently changed (within last 24 hours)
     */
    public boolean isPasswordRecentlyChanged() {
        return passwordChangedAt != null && 
               passwordChangedAt.isAfter(LocalDateTime.now().minusHours(24));
    }
    
    /**
     * Check if the user has logged in recently (within last 30 days)
     */
    public boolean hasRecentLogin() {
        return lastLoginAt != null && 
               lastLoginAt.isAfter(LocalDateTime.now().minusDays(30));
    }
    
    /**
     * Increment failed login attempts
     */
    public void incrementFailedAttempts() {
        this.failedLoginAttempts = (this.failedLoginAttempts != null) ? this.failedLoginAttempts + 1 : 1;
    }
    
    /**
     * Reset failed login attempts (called on successful login)
     */
    public void resetFailedAttempts() {
        this.failedLoginAttempts = 0;
        this.lockedUntil = null;
    }
    
    /**
     * Lock the account for a specific duration
     */
    public void lockAccount(int lockoutMinutes) {
        this.lockedUntil = LocalDateTime.now().plusMinutes(lockoutMinutes);
    }
    
    /**
     * Lock the account permanently
     */
    public void lockAccountPermanently() {
        this.lockedUntil = null;
        this.failedLoginAttempts = getMaxFailedAttempts();
    }
    
    /**
     * Unlock the account
     */
    public void unlockAccount() {
        this.lockedUntil = null;
        this.failedLoginAttempts = 0;
    }
    
    /**
     * Set password reset token with expiration
     */
    public void setPasswordResetToken(String token, int expirationHours) {
        this.passwordResetToken = token;
        this.passwordResetExpiresAt = LocalDateTime.now().plusHours(expirationHours);
    }
    
    /**
     * Clear password reset token
     */
    public void clearPasswordResetToken() {
        this.passwordResetToken = null;
        this.passwordResetExpiresAt = null;
    }
    
    /**
     * Set email verification token
     */
    public void setEmailVerificationToken(String token) {
        this.emailVerificationToken = token;
    }
    
    /**
     * Clear email verification token
     */
    public void clearEmailVerificationToken() {
        this.emailVerificationToken = null;
    }
    
    /**
     * Update password hash and set changed timestamp
     */
    public void updatePassword(String newPasswordHash) {
        this.passwordHash = newPasswordHash;
        this.passwordChangedAt = LocalDateTime.now();
        clearPasswordResetToken(); // Clear any pending reset tokens
    }
    
    /**
     * Record successful login
     */
    public void recordSuccessfulLogin() {
        this.lastLoginAt = LocalDateTime.now();
        resetFailedAttempts();
    }
    
    /**
     * Enable two-factor authentication
     */
    public void enableTwoFactor() {
        this.twoFactorEnabled = true;
    }
    
    /**
     * Disable two-factor authentication
     */
    public void disableTwoFactor() {
        this.twoFactorEnabled = false;
    }
    
    /**
     * Get the maximum allowed failed attempts before lockout
     */
    public int getMaxFailedAttempts() {
        return 5; // This could be configurable
    }
    
    /**
     * Get the lockout duration in minutes based on failed attempts
     */
    public int getLockoutDurationMinutes() {
        if (failedLoginAttempts == null) return 0;
        
        return switch (failedLoginAttempts) {
            case 3 -> 5;    // 5 minutes after 3 failed attempts
            case 4 -> 15;   // 15 minutes after 4 failed attempts
            case 5 -> 60;   // 1 hour after 5 failed attempts
            default -> failedLoginAttempts >= 6 ? 1440 : 0; // 24 hours for 6+ attempts
        };
    }
    
    /**
     * Check if credentials need security review (old password, many failed attempts, etc.)
     */
    public boolean needsSecurityReview() {
        return isPermanentlyLocked() || 
               (passwordChangedAt != null && passwordChangedAt.isBefore(LocalDateTime.now().minusMonths(6))) ||
               (failedLoginAttempts != null && failedLoginAttempts >= 3) ||
               (!hasRecentLogin() && lastLoginAt != null);
    }
    
    /**
     * Get security status description
     */
    public String getSecurityStatus() {
        if (isPermanentlyLocked()) return "PERMANENTLY_LOCKED";
        if (isAccountLocked()) return "TEMPORARILY_LOCKED";
        if (failedLoginAttempts != null && failedLoginAttempts > 0) return "FAILED_ATTEMPTS";
        if (twoFactorEnabled) return "SECURED_2FA";
        if (hasRecentLogin()) return "ACTIVE";
        return "INACTIVE";
    }
    
    @PrePersist
    public void prePersist() {
        if (createdAt == null) {
            createdAt = LocalDateTime.now();
        }
        if (updatedAt == null) {
            updatedAt = LocalDateTime.now();
        }
        if (failedLoginAttempts == null) {
            failedLoginAttempts = 0;
        }
        if (twoFactorEnabled == null) {
            twoFactorEnabled = false;
        }
    }
    
    @PreUpdate
    public void preUpdate() {
        updatedAt = LocalDateTime.now();
    }
}
