import { AuthAuditLog } from '#models/AuthAuditLog';
import { Op } from 'sequelize';

/**
 * Audit Logging Service
 *
 * Logs all authentication and authorization events for security monitoring and compliance.
 *
 * Event Types:
 * - LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT
 * - TOKEN_REFRESHED, TOKEN_ROTATED, TOKEN_REUSE_DETECTED
 * - SESSION_CREATED, SESSION_REVOKED, SESSION_EXPIRED
 * - PASSWORD_CHANGED, PASSWORD_RESET_REQUESTED, PASSWORD_RESET_COMPLETED
 * - ROLE_CHANGED, PERMISSION_CHANGED
 * - ACCOUNT_LOCKED, ACCOUNT_UNLOCKED
 * - MFA_ENABLED, MFA_DISABLED, MFA_VERIFICATION_FAILED
 *
 * Retention Policy:
 * - Active logs: 90 days
 * - Archive: 1 year
 * - Compliance: 7 years (for regulated industries)
 */

export type AuthEventType =
	| 'LOGIN_SUCCESS'
	| 'LOGIN_FAILED'
	| 'LOGOUT'
	| 'TOKEN_REFRESHED'
	| 'TOKEN_ROTATED'
	| 'TOKEN_REUSE_DETECTED'
	| 'SESSION_CREATED'
	| 'SESSION_REVOKED'
	| 'SESSION_EXPIRED'
	| 'PASSWORD_CHANGED'
	| 'PASSWORD_RESET_REQUESTED'
	| 'PASSWORD_RESET_COMPLETED'
	| 'ROLE_CHANGED'
	| 'PERMISSION_CHANGED'
	| 'ACCOUNT_LOCKED'
	| 'ACCOUNT_UNLOCKED'
	| 'MFA_ENABLED'
	| 'MFA_DISABLED'
	| 'MFA_VERIFICATION_FAILED';

export interface AuditLogData {
	userId?: string | undefined;
	action: AuthEventType;
	sessionId?: string | undefined;
	ipAddress?: string | undefined;
	userAgent?: string | undefined;
	deviceInfo?: Record<string, unknown> | undefined;
	success?: boolean | undefined;
	failureReason?: string | undefined;
	metadata?: Record<string, unknown> | undefined;
}

export class AuditLogService {
	/**
	 * Log an authentication event
	 */
	static async log(data: AuditLogData): Promise<void> {
		const {
			userId,
			action,
			sessionId,
			ipAddress,
			userAgent,
			deviceInfo,
			success,
			failureReason,
			metadata: _metadata,
		} = data;

		await AuthAuditLog.create({
			id: crypto.randomUUID(),
			user_id: userId || null,
			action,
			session_id: sessionId || null,
			ip_address: ipAddress || null,
			user_agent: userAgent || null,
			device_info: deviceInfo || null,
			success: success !== undefined ? success : true,
			failure_reason: failureReason || null,
			event_version: 1,
			created_at: new Date(),
		}).catch((error) => {
			// Log errors but don't throw - audit logging shouldn't break the application
			console.error('Failed to log audit event:', error);
		});
	}

	/**
	 * Log successful login
	 */
	static async logLoginSuccess(
		userId: string,
		sessionId: string,
		ipAddress?: string,
		userAgent?: string,
		deviceInfo?: Record<string, unknown>,
	): Promise<void> {
		await this.log({
			userId,
			action: 'LOGIN_SUCCESS',
			sessionId,
			ipAddress,
			userAgent,
			deviceInfo,
			success: true,
		});
	}

	/**
	 * Log failed login attempt
	 */
	static async logLoginFailed(
		userId: string | null,
		ipAddress?: string,
		userAgent?: string,
		deviceInfo?: Record<string, unknown>,
		failureReason?: string,
	): Promise<void> {
		await this.log({
			userId: userId || undefined,
			action: 'LOGIN_FAILED',
			ipAddress,
			userAgent,
			deviceInfo,
			success: false,
			failureReason,
		});
	}

	/**
	 * Log logout
	 */
	static async logLogout(userId: string, sessionId: string, ipAddress?: string, userAgent?: string): Promise<void> {
		await this.log({
			userId,
			action: 'LOGOUT',
			sessionId,
			ipAddress,
			userAgent,
			success: true,
		});
	}

	/**
	 * Log token refresh
	 */
	static async logTokenRefreshed(
		userId: string,
		sessionId: string,
		ipAddress?: string,
		userAgent?: string,
	): Promise<void> {
		await this.log({
			userId,
			action: 'TOKEN_REFRESHED',
			sessionId,
			ipAddress,
			userAgent,
			success: true,
		});
	}

	/**
	 * Log token rotation
	 */
	static async logTokenRotated(
		userId: string,
		sessionId: string,
		ipAddress?: string,
		userAgent?: string,
	): Promise<void> {
		await this.log({
			userId,
			action: 'TOKEN_ROTATED',
			sessionId,
			ipAddress,
			userAgent,
			success: true,
		});
	}

	/**
	 * Log token reuse detected
	 */
	static async logTokenReuseDetected(
		userId: string,
		ipAddress?: string,
		userAgent?: string,
		metadata?: Record<string, unknown>,
	): Promise<void> {
		await this.log({
			userId,
			action: 'TOKEN_REUSE_DETECTED',
			ipAddress,
			userAgent,
			success: false,
			failureReason: 'Refresh token reuse detected - all sessions revoked',
			metadata,
		});
	}

	/**
	 * Log session creation
	 */
	static async logSessionCreated(
		userId: string,
		sessionId: string,
		ipAddress?: string,
		userAgent?: string,
		deviceInfo?: Record<string, unknown>,
	): Promise<void> {
		await this.log({
			userId,
			action: 'SESSION_CREATED',
			sessionId,
			ipAddress,
			userAgent,
			deviceInfo,
			success: true,
		});
	}

	/**
	 * Log session revocation
	 */
	static async logSessionRevoked(
		userId: string,
		sessionId: string,
		reason?: string,
		ipAddress?: string,
		userAgent?: string,
	): Promise<void> {
		await this.log({
			userId,
			action: 'SESSION_REVOKED',
			sessionId,
			ipAddress,
			userAgent,
			success: true,
			metadata: { reason },
		});
	}

	/**
	 * Log password change
	 */
	static async logPasswordChanged(userId: string, ipAddress?: string, userAgent?: string): Promise<void> {
		await this.log({
			userId,
			action: 'PASSWORD_CHANGED',
			ipAddress,
			userAgent,
			success: true,
		});
	}

	/**
	 * Log password reset request
	 */
	static async logPasswordResetRequested(userId: string, ipAddress?: string, userAgent?: string): Promise<void> {
		await this.log({
			userId,
			action: 'PASSWORD_RESET_REQUESTED',
			ipAddress,
			userAgent,
			success: true,
		});
	}

	/**
	 * Log password reset completed
	 */
	static async logPasswordResetCompleted(userId: string, ipAddress?: string, userAgent?: string): Promise<void> {
		await this.log({
			userId,
			action: 'PASSWORD_RESET_COMPLETED',
			ipAddress,
			userAgent,
			success: true,
		});
	}

	/**
	 * Log role change
	 */
	static async logRoleChanged(
		userId: string,
		oldRoles: string[],
		newRoles: string[],
		ipAddress?: string,
		userAgent?: string,
	): Promise<void> {
		await this.log({
			userId,
			action: 'ROLE_CHANGED',
			ipAddress,
			userAgent,
			success: true,
			metadata: { oldRoles, newRoles },
		});
	}

	/**
	 * Log permission change
	 */
	static async logPermissionChanged(
		userId: string,
		changedPermissions: string[],
		ipAddress?: string,
		userAgent?: string,
	): Promise<void> {
		await this.log({
			userId,
			action: 'PERMISSION_CHANGED',
			ipAddress,
			userAgent,
			success: true,
			metadata: { changedPermissions },
		});
	}

	/**
	 * Log account lock
	 */
	static async logAccountLocked(userId: string, reason: string, ipAddress?: string, userAgent?: string): Promise<void> {
		await this.log({
			userId,
			action: 'ACCOUNT_LOCKED',
			ipAddress,
			userAgent,
			success: true,
			metadata: { reason },
		});
	}

	/**
	 * Log account unlock
	 */
	static async logAccountUnlocked(userId: string, ipAddress?: string, userAgent?: string): Promise<void> {
		await this.log({
			userId,
			action: 'ACCOUNT_UNLOCKED',
			ipAddress,
			userAgent,
			success: true,
		});
	}

	/**
	 * Query audit logs for a user
	 */
	static async getUserAuditLogs(userId: string, limit: number = 100, offset: number = 0): Promise<AuthAuditLog[]> {
		return AuthAuditLog.findAll({
			where: { user_id: userId },
			order: [['created_at', 'DESC']],
			limit,
			offset,
		});
	}

	/**
	 * Query audit logs by action type
	 */
	static async getLogsByAction(
		action: AuthEventType,
		limit: number = 100,
		offset: number = 0,
	): Promise<AuthAuditLog[]> {
		return AuthAuditLog.findAll({
			where: { action },
			order: [['created_at', 'DESC']],
			limit,
			offset,
		});
	}

	/**
	 * Query failed login attempts for a user
	 */
	static async getFailedLoginAttempts(
		userId: string,
		since: Date = new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
	): Promise<number> {
		return AuthAuditLog.count({
			where: {
				user_id: userId,
				action: 'LOGIN_FAILED',
				success: false,
				created_at: { [Op.gte]: since },
			},
		});
	}

	/**
	 * Archive old audit logs (for retention policy)
	 * This should be run as a scheduled job
	 *
	 * Note: With partition-based storage, archiving is handled by dropping old partitions
	 * via the AuditLogMaintenanceJob. This method is kept for backward compatibility
	 * but delegates to the partition-based approach.
	 */
	static async archiveOldLogs(daysToKeep: number = 90): Promise<number> {
		const cutoffDate = new Date(Date.now() - daysToKeep * 24 * 60 * 60 * 1000);

		// If using partition-based storage, archiving is handled by dropping old partitions
		// This method is kept for backward compatibility but logs a warning
		console.warn('archiveOldLogs is deprecated. Use AuditLogMaintenanceJob.runAll() for partition-based archival.');

		// For non-partitioned setups or backward compatibility, delete old logs
		const deleted = await AuthAuditLog.destroy({
			where: {
				created_at: { [Op.lt]: cutoffDate },
			},
		});

		return deleted;
	}
}
