import { UserSession } from '#models/UserSession';
import { DeviceTrustService } from './deviceTrustService';
import NotificationService from '#services/notification/notificationService';
import Logger from '#utils/logger';
import RedisService from '#services/storage/redisService';

/**
 * Anomaly Detection Service
 *
 * Detects and alerts about suspicious authentication patterns using:
 * - Geo region changes
 * - ASN/network family changes
 * - Impossible travel detection
 * - Device trust score drops
 * - Too many refresh attempts
 */

export interface AnomalyEvent {
	type:
		| 'new_geo_region'
		| 'impossible_travel'
		| 'asn_change'
		| 'device_trust_drop'
		| 'too_many_refreshes'
		| 'multiple_failures';
	userId: string;
	sessionId?: string;
	details: Record<string, unknown>;
	severity: 'low' | 'medium' | 'high';
	timestamp: Date;
}

export interface GeoLocation {
	countryCode: string;
	region?: string;
	city?: string;
}

export interface ASNDetection {
	asn: string;
	organization: string;
}

const _ANOMALY_CACHE_TTL = 1800; // 30 minutes
const _REFRESH_RATE_LIMIT = 10; // Max refreshes per minute
const _FAILURE_RATE_LIMIT = 5; // Max failed logins per 5 minutes
const _REFRESH_WINDOW_SECONDS = 60; // 1 minute window for refresh tracking
const _FAILURE_WINDOW_SECONDS = 300; // 5 minute window for failure tracking

export class AnomalyDetectionService {
	/**
	 * Get geo region from IP address
	 * Note: This is a placeholder - integrate with MaxMind GeoIP, IP2Location, or similar service
	 *
	 * Recommended implementation:
	 * 1. Install: npm install @maxmind/geoip2-node
	 * 2. Download MaxMind GeoLite2 database: https://dev.maxmind.com/geoip/geolite2-free-geolocation-data
	 * 3. Use the database to lookup IP addresses
	 *
	 * Alternative: Use IPinfo.io API (requires API key)
	 */
	private static getGeoRegion(_ip: string): string {
		// Placeholder implementation - returns 'UNKNOWN' for all IPs
		// To enable geolocation-based anomaly detection:
		// 1. Install geolocation library (e.g., @maxmind/geoip2-node)
		// 2. Configure database file path in environment variables
		// 3. Implement actual lookup here
		return 'UNKNOWN';
	}

	/**
	 * Get ASN from IP address
	 * Note: This is a placeholder - integrate with MaxMind ASN, IPinfo, or similar service
	 *
	 * Recommended implementation:
	 * 1. Use MaxMind GeoLite2 ASN database (free)
	 * 2. Or use IPinfo.io API (requires API key)
	 * 3. Returns ASN and organization for the IP
	 */
	private static getASN(_ip: string): ASNDetection {
		// Placeholder implementation - returns 'UNKNOWN' for all IPs
		// To enable ASN-based anomaly detection:
		// 1. Install geolocation library with ASN support
		// 2. Configure database file path in environment variables
		// 3. Implement actual lookup here
		return { asn: 'UNKNOWN', organization: 'UNKNOWN' };
	}

	/**
	 * Calculate distance between two geo regions (simplified)
	 * Note: This is a placeholder - implement actual distance calculation
	 *
	 * Recommended implementation:
	 * 1. Get lat/long coordinates for both locations
	 * 2. Use Haversine formula to calculate distance
	 * 3. Return distance in kilometers
	 */
	private static calculateGeoDistance(geo1: string, geo2: string): number {
		// Simplified placeholder - assumes 1000km if different countries
		// To enable accurate impossible travel detection:
		// 1. Implement actual lat/long coordinate lookup
		// 2. Use Haversine formula for distance calculation
		return geo1 === geo2 ? 0 : 1000;
	}

	/**
	 * Detect anomalies for a new login session
	 */
	static async detectLoginAnomalies(
		userId: string,
		sessionId: string,
		ipAddress: string,
		deviceId?: string,
	): Promise<AnomalyEvent[]> {
		const anomalies: AnomalyEvent[] = [];

		// Get user's recent sessions
		const recentSessions = await UserSession.findAll({
			where: {
				user_id: userId,
				status: 'active',
			},
			order: [['last_activity_at', 'DESC']],
			limit: 5,
		});

		if (recentSessions.length === 0) {
			// First login - no anomalies to detect
			return anomalies;
		}

		const lastSession = recentSessions[0];
		if (!lastSession || !lastSession.ip_address) {
			// No session or no IP address - skip IP-based checks
			return anomalies;
		}

		const currentGeo = this.getGeoRegion(ipAddress);
		const lastGeo = this.getGeoRegion(lastSession.ip_address);
		const currentASN = this.getASN(ipAddress);
		const lastASN = this.getASN(lastSession.ip_address);

		// Geo region change detection
		if (currentGeo !== lastGeo && currentGeo !== 'UNKNOWN' && lastGeo !== 'UNKNOWN') {
			const distance = this.calculateGeoDistance(currentGeo, lastGeo);
			const timeDiff = Date.now() - (lastSession.last_activity_at?.getTime() || Date.now());
			const maxDistance = (timeDiff / 1000 / 3600) * 1000; // 1000km/h max speed

			if (distance > maxDistance && distance > 1000) {
				// Impossible travel
				anomalies.push({
					type: 'impossible_travel',
					userId,
					sessionId,
					details: {
						distance,
						timeDiff,
						lastGeo,
						currentGeo,
						lastIP: lastSession.ip_address,
						currentIP: ipAddress,
					},
					severity: 'high',
					timestamp: new Date(),
				});
			} else {
				// New geo region (not impossible travel)
				anomalies.push({
					type: 'new_geo_region',
					userId,
					sessionId,
					details: {
						lastGeo,
						currentGeo,
						lastIP: lastSession.ip_address,
						currentIP: ipAddress,
					},
					severity: 'medium',
					timestamp: new Date(),
				});
			}
		}

		// ASN change detection (carrier switch)
		if (currentASN.asn !== lastASN.asn && currentASN.asn !== 'UNKNOWN' && lastASN.asn !== 'UNKNOWN') {
			anomalies.push({
				type: 'asn_change',
				userId,
				sessionId,
				details: {
					lastASN: lastASN.asn,
					lastOrganization: lastASN.organization,
					currentASN: currentASN.asn,
					currentOrganization: currentASN.organization,
				},
				severity: 'low',
				timestamp: new Date(),
			});
		}

		// Device trust score check
		if (deviceId) {
			const trustScore = await DeviceTrustService.calculateTrustScore({
				deviceId,
				userId,
				currentASN: currentASN.asn,
				currentGeoRegion: currentGeo,
			});

			// Check for trust score drop (compare with cached previous score)
			// This would require storing previous scores - simplified here
			if (trustScore.score < 50) {
				anomalies.push({
					type: 'device_trust_drop',
					userId,
					sessionId,
					details: {
						deviceId,
						currentScore: trustScore.score,
						factors: trustScore.factors,
					},
					severity: 'medium',
					timestamp: new Date(),
				});
			}
		}

		// Send security notifications for high/medium severity anomalies
		for (const anomaly of anomalies) {
			if (anomaly.severity === 'high' || anomaly.severity === 'medium') {
				this.sendSecurityNotification(userId, anomaly).catch((error) => {
					Logger.error('Failed to send security notification for anomaly', error);
				});
			}
		}

		return anomalies;
	}

	/**
	 * Detect refresh token abuse
	 */
	static async detectRefreshTokenAbuse(userId: string, sessionId: string): Promise<boolean> {
		const redis = RedisService.getInstance();
		const key = `refresh_abuse:${userId}:${sessionId}`;

		try {
			const count = await redis.incrementWithExpiry(key, _REFRESH_WINDOW_SECONDS);
			if (count > _REFRESH_RATE_LIMIT) {
				Logger.warn(`Refresh token abuse detected for user ${userId}`, { userId, sessionId, count });
				return true;
			}
			return false;
		} catch (error) {
			Logger.error('Failed to track refresh attempts in Redis', error);
			// Fail-open: allow refresh if Redis is unavailable
			return false;
		}
	}

	/**
	 * Detect failed login abuse
	 */
	static async detectFailedLoginAbuse(userId: string, ipAddress: string): Promise<boolean> {
		const redis = RedisService.getInstance();
		const key = `login_failure:${userId}:${ipAddress}`;

		try {
			const count = await redis.incrementWithExpiry(key, _FAILURE_WINDOW_SECONDS);
			if (count > _FAILURE_RATE_LIMIT) {
				Logger.warn(`Failed login abuse detected for user ${userId} from IP ${ipAddress}`, {
					userId,
					ipAddress,
					count,
				});
				return true;
			}
			return false;
		} catch (error) {
			Logger.error('Failed to track login failures in Redis', error);
			// Fail-open: allow login attempt if Redis is unavailable
			return false;
		}
	}

	/**
	 * Send security notification for anomaly
	 */
	private static async sendSecurityNotification(userId: string, anomaly: AnomalyEvent): Promise<void> {
		const messages: Record<string, string> = {
			new_geo_region: 'Phát hiện đăng nhập từ khu vực địa lý mới',
			impossible_travel: 'Phát hiện hoạt động bất thường: du lịch không thể',
			asn_change: 'Phát hiện thay đổi nhà mạng',
			device_trust_drop: 'Điểm tin cậy thiết bị giảm',
			too_many_refreshes: 'Phát hiện quá nhiều lần làm mới token',
			multiple_failures: 'Phát hiện nhiều lần đăng nhập thất bại',
		};

		await NotificationService.getInstance().createAndSend({
			userId,
			title: messages[anomaly.type] || 'Phát hiện hoạt động bất thường',
			body: `Chi tiết: ${JSON.stringify(anomaly.details)}`,
			type: 'security_alert',
			channel: 'in_app',
			data: anomaly as unknown as Record<string, unknown>,
		});
	}

	/**
	 * Check if action should be blocked based on anomalies
	 */
	static shouldBlockAction(anomalies: AnomalyEvent[]): boolean {
		return anomalies.some((a) => a.severity === 'high');
	}

	/**
	 * Check if additional verification is required based on anomalies
	 */
	static requiresAdditionalVerification(anomalies: AnomalyEvent[]): boolean {
		return anomalies.some((a) => a.severity === 'medium' || a.severity === 'high');
	}
}
