package com.gnx.telemedicine.service;

import com.gnx.telemedicine.exception.ConflictException;
import com.gnx.telemedicine.exception.ResourceNotFoundException;
import com.gnx.telemedicine.model.PasswordHistory;
import com.gnx.telemedicine.model.UserModel;
import com.gnx.telemedicine.repository.PasswordHistoryRepository;
import com.gnx.telemedicine.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.List;

/**
 * Service for managing password policies including history and expiration.
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class PasswordPolicyService {
    
    private final PasswordHistoryRepository passwordHistoryRepository;
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    
    @Value("${security.password.history-count:5}")
    private int defaultHistoryCount;
    
    @Value("${security.password.expiration-days:90}")
    private int defaultExpirationDays;
    
    @Value("${security.password.enforce-history:true}")
    private boolean enforceHistory;
    
    @Value("${security.password.enforce-expiration:true}")
    private boolean enforceExpiration;
    
    /**
     * Check if password has been used recently (in password history).
     */
    public boolean isPasswordInHistory(UserModel user, String newPassword) {
        if (!enforceHistory) {
            return false;
        }
        
        // Check if new password matches current password
        if (user.getPassword() != null && passwordEncoder.matches(newPassword, user.getPassword())) {
            return true; // New password matches current password
        }
        
        // Check against password history
        List<PasswordHistory> history = passwordHistoryRepository.findRecentPasswordsByUser(user);
        int historyCount = user.getPasswordHistoryCount() != null ? user.getPasswordHistoryCount() : defaultHistoryCount;
        
        // Only check the last N passwords
        int checkCount = Math.min(historyCount, history.size());
        for (int i = 0; i < checkCount; i++) {
            PasswordHistory entry = history.get(i);
            if (passwordEncoder.matches(newPassword, entry.getPasswordHash())) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Save password to history.
     */
    @Transactional(rollbackFor = {Exception.class})
    public void savePasswordToHistory(UserModel user, String oldPasswordHash) {
        // Save current password to history
        PasswordHistory historyEntry = PasswordHistory.builder()
                .user(user)
                .passwordHash(oldPasswordHash)
                .createdAt(Instant.now())
                .build();
        
        passwordHistoryRepository.save(historyEntry);
        
        // Clean up old history entries (keep only last N passwords)
        int historyCount = user.getPasswordHistoryCount() != null ? user.getPasswordHistoryCount() : defaultHistoryCount;
        List<PasswordHistory> allHistory = passwordHistoryRepository.findRecentPasswordsByUser(user);
        
        if (allHistory.size() > historyCount) {
            // Delete oldest entries
            List<PasswordHistory> toDelete = allHistory.subList(historyCount, allHistory.size());
            passwordHistoryRepository.deleteAll(toDelete);
            log.debug("Cleaned up {} old password history entries for user: {}", toDelete.size(), user.getEmail());
        }
    }
    
    /**
     * Update password with history tracking and expiration.
     */
    @Transactional(rollbackFor = {Exception.class})
    public void updatePassword(UserModel user, String newPassword) {
        // Save old password to history
        if (user.getPassword() != null) {
            savePasswordToHistory(user, user.getPassword());
        }
        
        // Update password
        String newPasswordHash = passwordEncoder.encode(newPassword);
        user.setPassword(newPasswordHash);
        
        // Update password change timestamp
        Instant now = Instant.now();
        user.setPasswordChangedAt(now);
        
        // Calculate expiration date
        int expirationDays = user.getPasswordExpirationDays() != null ? user.getPasswordExpirationDays() : defaultExpirationDays;
        user.setPasswordExpiresAt(now.plusSeconds(expirationDays * 24L * 60L * 60L));
        
        userRepository.save(user);
        
        log.debug("Password updated for user: {} - Expires at: {}", user.getEmail(), user.getPasswordExpiresAt());
    }
    
    /**
     * Check if password is expired.
     */
    public boolean isPasswordExpired(UserModel user) {
        if (!enforceExpiration) {
            return false;
        }
        
        if (user.getPasswordExpiresAt() == null) {
            // If expiration is not set, calculate it from password_changed_at
            if (user.getPasswordChangedAt() != null) {
                int expirationDays = user.getPasswordExpirationDays() != null ? user.getPasswordExpirationDays() : defaultExpirationDays;
                Instant expirationDate = user.getPasswordChangedAt().plusSeconds(expirationDays * 24L * 60L * 60L);
                return expirationDate.isBefore(Instant.now());
            }
            return false; // No expiration set and no change date
        }
        
        return user.getPasswordExpiresAt().isBefore(Instant.now());
    }
    
    /**
     * Get days until password expires.
     */
    public long getDaysUntilExpiration(UserModel user) {
        if (user.getPasswordExpiresAt() == null) {
            return -1; // No expiration set
        }
        
        Instant now = Instant.now();
        if (user.getPasswordExpiresAt().isBefore(now)) {
            return 0; // Already expired
        }
        
        long secondsUntilExpiration = user.getPasswordExpiresAt().getEpochSecond() - now.getEpochSecond();
        return secondsUntilExpiration / (24 * 60 * 60);
    }
    
    /**
     * Validate password against policy (history and complexity).
     */
    public void validatePasswordPolicy(UserModel user, String newPassword) {
        // Check if password is in history
        if (isPasswordInHistory(user, newPassword)) {
            int historyCount = user.getPasswordHistoryCount() != null ? user.getPasswordHistoryCount() : defaultHistoryCount;
            throw new ConflictException(
                String.format("Password has been used recently. Please choose a different password. You cannot reuse your last %d passwords.", historyCount)
            );
        }
    }
    
    /**
     * Clean up old password history entries (older than specified days).
     */
    @Transactional(rollbackFor = {Exception.class})
    public void cleanupOldPasswordHistory(int daysToKeep) {
        Instant cutoffDate = Instant.now().minusSeconds(daysToKeep * 24L * 60L * 60L);
        passwordHistoryRepository.deleteOldPasswordHistory(cutoffDate);
        log.info("Cleaned up password history entries older than {} days", daysToKeep);
    }
}

