import { createHash } from 'crypto';
import Logger from '#utils/logger';
import Config from '#config';

/**
 * Password Validation Service
 *
 * NIST SP 800-63B compliant password validation:
 * - Length > forced complexity
 * - Passphrase support
 * - Breached password check
 * - No personal info
 */

export interface UserInfo {
	email: string;
	username?: string;
	firstName?: string;
	lastName?: string;
}

export interface ValidationResult {
	valid: boolean;
	reason?: string;
	suggestions?: string[];
	strength?: number; // 0-4 score from zxcvbn
	field?: string; // Field name for automatic error mapping
}

export interface PasswordPolicy {
	minLength: number;
	maxLength: number;
	checkBreachedPasswords: boolean;
	blockPersonalInfo: boolean;
}

const DEFAULT_POLICY: PasswordPolicy = {
	minLength: Config.auth.passwordMinLength,
	maxLength: Config.auth.passwordMaxLength,
	checkBreachedPasswords: true,
	blockPersonalInfo: true,
};

// Common breached passwords list (simplified - in production, use Have I Been Pwned API)
// SEC-04: Expanded list based on most common breached passwords
const COMMON_BREACHED_PASSWORDS = new Set([
	'password',
	'123456',
	'12345678',
	'qwerty',
	'abc123',
	'password123',
	'admin',
	'welcome',
	'letmein',
	'login',
	'secret',
	'123456789',
	'1234567',
	'1234567890',
	'iloveyou',
	'princess',
	'rockyou',
	'12345678910',
	'111111',
	'000000',
	'sunshine',
	'master',
	'football',
	'baseball',
	'soccer',
	'hockey',
	'angel',
	' trustno1',
	'charlie',
	'qwerty123',
	'qwertyuiop',
	'123qwe',
	'passw0rd',
	'password1',
	'admin123',
	'root',
	'test',
	'guest',
	'user',
	'demo',
	'123abc',
	'1234',
	'12345',
	'12345678912',
	'access',
	'work',
	'home',
	'money',
	'love',
	'family',
	'god',
	'jesus',
	'michael',
	'jordan',
	'matthew',
	'daniel',
	'andrew',
	'joshua',
	'christopher',
	'joseph',
	'david',
	'james',
	'john',
	'robert',
	'michael',
	'william',
	'richard',
	'thomas',
	'charles',
	'donald',
	'patricia',
	'linda',
	'barbara',
	'elizabeth',
	'jennifer',
	'maria',
	'susan',
	'margaret',
	'dorothy',
	'lisa',
	'nancy',
	'karen',
	'betty',
	'helen',
	'sandra',
	'donna',
	'carol',
	'ruth',
	'sharon',
	'michelle',
	'laura',
	'sarah',
	'kimberly',
	'deborah',
	'jessica',
	'sharon',
	'cynthia',
	'kathleen',
	'amy',
	'shirley',
	'angela',
	'helen',
	'anna',
	'brenda',
	'pamela',
	'nicole',
	'emma',
	'samantha',
	'katherine',
	'christine',
	'deborah',
	'rebecca',
]);

export class PasswordValidationService {
	private static policy: PasswordPolicy = { ...DEFAULT_POLICY };

	/**
	 * Set custom password policy
	 */
	static setPolicy(policy: Partial<PasswordPolicy>): void {
		this.policy = { ...this.policy, ...policy };
	}

	/**
	 * Get current password policy
	 */
	static getPolicy(): PasswordPolicy {
		return { ...this.policy };
	}

	/**
	 * Validate password against policy
	 */
	static async validatePassword(password: string, userInfo: UserInfo): Promise<ValidationResult> {
		// 1. Length check
		if (password.length < this.policy.minLength) {
			return {
				valid: false,
				reason: 'PASSWORD_TOO_SHORT',
				field: 'password',
				suggestions: [
					`Password must be at least ${this.policy.minLength} characters long`,
					`Mật khẩu phải có ít nhất ${this.policy.minLength} ký tự`,
				],
			};
		}

		if (password.length > this.policy.maxLength) {
			return {
				valid: false,
				reason: 'PASSWORD_TOO_LONG',
				field: 'password',
				suggestions: [
					`Password must not exceed ${this.policy.maxLength} characters`,
					`Mật khẩu không được vượt quá ${this.policy.maxLength} ký tự`,
				],
			};
		}

		// 2. Breached password check
		if (this.policy.checkBreachedPasswords) {
			const isBreached = await this.checkBreachedPassword(password);
			if (isBreached) {
				return {
					valid: false,
					reason: 'PASSWORD_BREACHED',
					field: 'password',
					suggestions: [
						'This password has been found in data breaches. Please choose a different password.',
						'Mật khẩu này đã bị lộ trong các vụ rò rỉ dữ liệu. Vui lòng chọn mật khẩu khác.',
					],
				};
			}
		}

		// 3. Personal info check
		if (this.policy.blockPersonalInfo) {
			const containsPersonalInfo = this.checkPersonalInfo(password, userInfo);
			if (containsPersonalInfo) {
				return {
					valid: false,
					reason: 'PASSWORD_CONTAINS_PERSONAL_INFO',
					field: 'password',
					suggestions: [
						'Password should not contain your email, username, name, or other personal information.',
						'Mật khẩu không được chứa email, tên đăng nhập, tên hoặc thông tin cá nhân khác.',
					],
				};
			}
		}

		// 4. Strength estimation (optional, for user feedback)
		// Note: zxcvbn-ts would be used here for actual strength estimation
		// For now, we'll use a simplified strength check
		// DISABLED: Rely on length, breached password, and personal info checks instead
		// const strength = this.estimateStrength(password);

		// if (strength < 2) {
		// 	return {
		// 		valid: false,
		// 		reason: 'PASSWORD_TOO_WEAK',
		// 		strength,
		// 		suggestions: [
		// 			'Consider using a longer passphrase with multiple words',
		// 			'Nên sử dụng passphrase dài hơn với nhiều từ',
		// 			'Avoid common words and patterns',
		// 			'Tránh các từ và pattern phổ biến',
		// 			'Try: correct-horse-battery-staple',
		// 			'Thử: correct-horse-battery-staple',
		// 		],
		// 	};
		// }

		return { valid: true, strength: 0 };
	}

	/**
	 * Check if password is in breached password database
	 * Uses Have I Been Pwned API with k-anonymity model
	 */
	private static async checkBreachedPassword(password: string): Promise<boolean> {
		// Check against common breached passwords list first (fast path)
		if (COMMON_BREACHED_PASSWORDS.has(password.toLowerCase())) {
			return true;
		}

		// Check against Have I Been Pwned API
		// Uses k-anonymity model: only first 5 chars of hash are sent to API
		try {
			// Hash password with SHA-1
			const hash = createHash('sha1').update(password).digest('hex').toUpperCase();
			const prefix = hash.substring(0, 5);
			const suffix = hash.substring(5);

			// Request from HIBP API with timeout
			// SEC-05: Increased from 2s to 5s to prevent false negatives from slow API responses
			const controller = new AbortController();
			const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
			const response = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`, {
				method: 'GET',
				headers: {
					'User-Agent': 'BeKind-Backend',
				},
				signal: controller.signal,
			});
			clearTimeout(timeoutId);

			if (!response.ok) {
				Logger.warn(`HIBP API returned ${response.status}, using fallback check`);
				return false;
			}

			const data = await response.text();
			const hashes = data.split('\n');

			// Check if our hash suffix is in the response
			for (const line of hashes) {
				const [suffixFromApi, count] = line.split(':');
				if (suffixFromApi === suffix && count) {
					const occurrenceCount = Number.parseInt(count, 10);
					Logger.warn(`Password found in ${occurrenceCount} breaches`);
					return true;
				}
			}

			return false;
		} catch (error) {
			Logger.error('[PasswordValidationService] Failed to check password against HIBP API', error);
			// If API fails, we still have the local common passwords check
			return false;
		}
	}

	/**
	 * Check if password contains personal information
	 */
	private static checkPersonalInfo(password: string, userInfo: UserInfo): boolean {
		const passwordLower = password.toLowerCase();
		const personalInfo = [userInfo.email, userInfo.username, userInfo.firstName, userInfo.lastName].filter(
			(info): info is string => info !== undefined,
		);

		return personalInfo.some((info) => passwordLower.includes(info.toLowerCase()));
	}

	/**
	 * Estimate password strength (simplified)
	 * Returns score 0-4 (higher is better)
	 * Note: In production, use zxcvbn-ts for accurate strength estimation
	 */
	private static estimateStrength(password: string): number {
		let score = 0;

		// Length bonus
		if (password.length >= 12) score += 1;
		if (password.length >= 16) score += 1;
		if (password.length >= 20) score += 1;

		// Character variety bonus (optional, not required by NIST)
		const hasLowercase = /[a-z]/.test(password);
		const hasUppercase = /[A-Z]/.test(password);
		const hasNumber = /[0-9]/.test(password);
		const hasSpecial = /[^a-zA-Z0-9]/.test(password);

		const varietyCount = [hasLowercase, hasUppercase, hasNumber, hasSpecial].filter(Boolean).length;
		if (varietyCount >= 3) score += 1;

		// Clamp to 0-4
		return Math.min(4, score);
	}

	/**
	 * Generate password strength feedback for user
	 */
	static getStrengthFeedback(strength: number): string {
		const feedback = [
			{ score: 0, label: 'Very Weak', color: 'red' },
			{ score: 1, label: 'Weak', color: 'orange' },
			{ score: 2, label: 'Fair', color: 'yellow' },
			{ score: 3, label: 'Good', color: 'lightgreen' },
			{ score: 4, label: 'Strong', color: 'green' },
		];

		const result = feedback.find((f) => f.score === strength);
		return result ? result.label : 'Unknown';
	}
}
