/**
 * Authentication Layer for Critical Operations
 * Provides secure authentication and authorization for sensitive operations
 */

import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import { CommandSanitizer } from './commandSanitizer';
import { resourceManager } from './resourceManager';
import { stateManager } from './threadSafeState';

export interface AuthenticationCredentials {
	userId: string;
	apiKey: string;
	sessionToken?: string;
	permissions: string[];
	expiresAt: number;
}

export interface AuthenticationResult {
	authenticated: boolean;
	authorized: boolean;
	userId?: string;
	permissions?: string[];
	reason?: string;
	sessionToken?: string;
}

export interface OperationContext {
	operation: string;
	resourceId?: string;
	requesterId?: string;
	timestamp: number;
	metadata?: Record<string, any>;
}

export class AuthenticationLayer {
	private static readonly SESSION_DURATION = 2 * 60 * 60 * 1000; // 2 hours
	private static readonly API_KEY_LENGTH = 64; // 64 chars hex
	private static readonly SESSION_TOKEN_LENGTH = 32; // 32 chars hex
	private static readonly MAX_SESSIONS_PER_USER = 5;
	private static readonly AUTH_CACHE_DURATION = 5 * 60 * 1000; // 5 minutes

	// Secure storage for active sessions
	private static activeSessions = stateManager.getMap<string, AuthenticationCredentials>('activeSessions');
	
	// Authentication cache to reduce overhead
	private static authCache = stateManager.getMap<string, { result: AuthenticationResult; cachedAt: number }>('authCache');

	// Critical operations that require authentication
	private static readonly CRITICAL_OPERATIONS = new Set([
		'createProject',
		'killSession', 
		'executeScheduledProject',
		'addTeamMember',
		'removeTeamMember',
		'blockCommit',
		'approveCommit',
		'generateQAKeyPair',
		'registerQAEngineer',
		'createCryptographicQAApproval'
	]);

	// Permission levels for different operations
	private static readonly OPERATION_PERMISSIONS = {
		'createProject': ['project.create', 'admin'],
		'killSession': ['session.kill', 'admin'],
		'executeScheduledProject': ['project.execute', 'admin'],
		'addTeamMember': ['team.manage', 'admin'],
		'removeTeamMember': ['team.manage', 'admin'],
		'blockCommit': ['qa.block', 'qa.engineer', 'admin'],
		'approveCommit': ['qa.approve', 'qa.engineer', 'admin'],
		'generateQAKeyPair': ['qa.keys', 'admin'],
		'registerQAEngineer': ['qa.register', 'admin'],
		'createCryptographicQAApproval': ['qa.approve', 'qa.engineer', 'admin']
	};

	/**
	 * Generate a new API key for a user
	 */
	public static generateApiKey(): string {
		return crypto.randomBytes(this.API_KEY_LENGTH / 2).toString('hex');
	}

	/**
	 * Generate a secure session token
	 */
	private static generateSessionToken(): string {
		return crypto.randomBytes(this.SESSION_TOKEN_LENGTH / 2).toString('hex');
	}

	/**
	 * Create authentication credentials for a user
	 */
	public static async createUserCredentials(
		userId: string,
		permissions: string[]
	): Promise<{ apiKey: string; userId: string; permissions: string[] }> {
		// Validate inputs
		if (!userId || typeof userId !== 'string') {
			throw new Error('Invalid user ID');
		}

		const userValidation = CommandSanitizer.validateProjectName(userId); // Reuse project name validation
		if (!userValidation.isValid) {
			throw new Error(`Invalid user ID format: ${userValidation.errors.join(', ')}`);
		}

		if (!Array.isArray(permissions)) {
			throw new Error('Permissions must be an array');
		}

		// Generate secure API key
		const apiKey = this.generateApiKey();

		// Store credentials securely
		const credentialsDir = '/var/n8n/auth';
		try {
			await fs.promises.access(credentialsDir);
		} catch {
			await fs.promises.mkdir(credentialsDir, { recursive: true, mode: 0o700 });
		}

		const credentialsPath = path.join(credentialsDir, `${userId}.json`);
		const credentials = {
			userId,
			apiKeyHash: crypto.createHash('sha256').update(apiKey).digest('hex'),
			permissions,
			createdAt: Date.now(),
			lastUsed: null
		};

		await fs.promises.writeFile(credentialsPath, JSON.stringify(credentials, null, 2), { mode: 0o600 });

		return { apiKey, userId, permissions };
	}

	/**
	 * Authenticate a user with API key
	 */
	public static async authenticateUser(
		userId: string,
		apiKey: string
	): Promise<AuthenticationResult> {
		try {
			// Input validation
			if (!userId || !apiKey) {
				return {
					authenticated: false,
					authorized: false,
					reason: 'Missing credentials'
				};
			}

			// Check cache first
			const cacheKey = `${userId}:${crypto.createHash('sha256').update(apiKey).digest('hex').substring(0, 16)}`;
			const cached = this.authCache.safeGet(cacheKey);
			if (cached && (Date.now() - cached.cachedAt) < this.AUTH_CACHE_DURATION) {
				return cached.result;
			}

			// Load user credentials
			const credentialsDir = '/var/n8n/auth';
			const credentialsPath = path.join(credentialsDir, `${userId}.json`);

			try {
				await fs.promises.access(credentialsPath);
			} catch {
				const result = {
					authenticated: false,
					authorized: false,
					reason: 'User not found'
				};
				await this.authCache.safeSet(cacheKey, { result, cachedAt: Date.now() });
				return result;
			}

			const credentials = JSON.parse(await fs.promises.readFile(credentialsPath, 'utf8'));

			// Verify API key
			const providedKeyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
			if (!crypto.timingSafeEqual(
				Buffer.from(credentials.apiKeyHash, 'hex'),
				Buffer.from(providedKeyHash, 'hex')
			)) {
				const result = {
					authenticated: false,
					authorized: false,
					reason: 'Invalid API key'
				};
				try {
					await this.authCache.safeSet(cacheKey, { result, cachedAt: Date.now() });
				} catch (error) {
					console.warn('Failed to cache auth result:', error.message);
				}
				return result;
			}

			// Generate session token
			const sessionToken = this.generateSessionToken();
			const expiresAt = Date.now() + this.SESSION_DURATION;

			// Clean up old sessions for this user
			await this.cleanupUserSessions(userId);

			// Store active session
			const sessionCredentials: AuthenticationCredentials = {
				userId,
				apiKey,
				sessionToken,
				permissions: credentials.permissions,
				expiresAt
			};

			try {
				await this.activeSessions.safeSet(sessionToken, sessionCredentials);
			} catch (error) {
				console.error('Failed to store active session:', error.message);
				throw new Error('Failed to create session');
			}

			// Update last used timestamp
			credentials.lastUsed = Date.now();
			await fs.promises.writeFile(credentialsPath, JSON.stringify(credentials, null, 2), { mode: 0o600 });

			const result = {
				authenticated: true,
				authorized: true,
				userId,
				permissions: credentials.permissions,
				sessionToken
			};

			try {
				await this.authCache.safeSet(cacheKey, { result, cachedAt: Date.now() });
			} catch (error) {
				console.warn('Failed to cache auth result:', error.message);
			}
			return result;

		} catch (error) {
			return {
				authenticated: false,
				authorized: false,
				reason: `Authentication error: ${error.message}`
			};
		}
	}

	/**
	 * Authenticate using session token
	 */
	public static authenticateSession(sessionToken: string): AuthenticationResult {
		try {
			if (!sessionToken) {
				return {
					authenticated: false,
					authorized: false,
					reason: 'Missing session token'
				};
			}

			const session = this.activeSessions.safeGet(sessionToken);
			if (!session) {
				return {
					authenticated: false,
					authorized: false,
					reason: 'Invalid session token'
				};
			}

			// Check expiration
			if (Date.now() > session.expiresAt) {
				// Note: We can't await in this sync method, so we fire and forget
				this.activeSessions.safeDelete(sessionToken).catch(error => {
					console.warn('Failed to delete expired session:', error.message);
				});
				return {
					authenticated: false,
					authorized: false,
					reason: 'Session expired'
				};
			}

			return {
				authenticated: true,
				authorized: true,
				userId: session.userId,
				permissions: session.permissions,
				sessionToken
			};

		} catch (error) {
			return {
				authenticated: false,
				authorized: false,
				reason: `Session authentication error: ${error.message}`
			};
		}
	}

	/**
	 * Authorize an operation for a user
	 */
	public static authorizeOperation(
		operation: string,
		userPermissions: string[]
	): { authorized: boolean; reason?: string } {
		try {
			// Check if operation requires authentication
			if (!this.CRITICAL_OPERATIONS.has(operation)) {
				return { authorized: true }; // Non-critical operations allowed
			}

			// Get required permissions for operation
			const requiredPermissions = this.OPERATION_PERMISSIONS[operation];
			if (!requiredPermissions) {
				return { authorized: false, reason: 'Unknown operation' };
			}

			// Check if user has any of the required permissions
			const hasPermission = requiredPermissions.some(perm => 
				userPermissions.includes(perm)
			);

			if (!hasPermission) {
				return {
					authorized: false,
					reason: `Missing required permissions: ${requiredPermissions.join(' or ')}`
				};
			}

			return { authorized: true };

		} catch (error) {
			return {
				authorized: false,
				reason: `Authorization error: ${error.message}`
			};
		}
	}

	/**
	 * Complete authentication and authorization check
	 */
	public static async authenticateAndAuthorize(
		operation: string,
		credentials: { userId?: string; apiKey?: string; sessionToken?: string },
		context?: OperationContext
	): Promise<AuthenticationResult> {
		try {
			let authResult: AuthenticationResult;

			// Try session authentication first
			if (credentials.sessionToken) {
				authResult = this.authenticateSession(credentials.sessionToken);
			}
			// Fall back to API key authentication
			else if (credentials.userId && credentials.apiKey) {
				authResult = await this.authenticateUser(credentials.userId, credentials.apiKey);
			}
			else {
				return {
					authenticated: false,
					authorized: false,
					reason: 'No valid credentials provided'
				};
			}

			// If authentication failed, return early
			if (!authResult.authenticated) {
				return authResult;
			}

			// Check authorization for the operation
			const authzResult = this.authorizeOperation(operation, authResult.permissions || []);
			
			if (!authzResult.authorized) {
				return {
					authenticated: true,
					authorized: false,
					userId: authResult.userId,
					permissions: authResult.permissions,
					reason: authzResult.reason
				};
			}

			// Log the operation for audit trail
			if (context) {
				await this.logOperation(authResult.userId!, operation, context);
			}

			return authResult;

		} catch (error) {
			return {
				authenticated: false,
				authorized: false,
				reason: `Authentication/authorization error: ${error.message}`
			};
		}
	}

	/**
	 * Log operation for audit trail
	 */
	private static async logOperation(
		userId: string,
		operation: string,
		context: OperationContext
	): Promise<void> {
		try {
			const logDir = '/var/log/n8n/auth';
			try {
				await fs.promises.access(logDir);
			} catch {
				await fs.promises.mkdir(logDir, { recursive: true, mode: 0o700 });
			}

			const logEntry = {
				timestamp: new Date().toISOString(),
				userId,
				operation,
				context: {
					resourceId: context.resourceId,
					requesterId: context.requesterId,
					metadata: context.metadata
				},
				auditHash: ''
			};

			// Generate audit hash
			logEntry.auditHash = crypto
				.createHash('sha256')
				.update(JSON.stringify(logEntry, Object.keys(logEntry).sort()))
				.digest('hex');

			const logPath = path.join(logDir, `operations_${new Date().toISOString().split('T')[0]}.log`);
			await fs.promises.appendFile(logPath, JSON.stringify(logEntry) + '\n', { mode: 0o600 });

		} catch (error) {
			console.warn('Failed to log operation:', error.message);
		}
	}

	/**
	 * Clean up expired sessions and old user sessions
	 */
	private static async cleanupUserSessions(userId: string): Promise<void> {
		try {
			const userSessions = this.activeSessions.safeEntries()
				.filter(([_, session]) => session.userId === userId);

			// Remove expired sessions
			for (const [token, session] of userSessions) {
				if (Date.now() > session.expiresAt) {
					try {
						await this.activeSessions.safeDelete(token);
					} catch (error) {
						console.warn(`Failed to delete expired session ${token}:`, error.message);
					}
				}
			}

			// Limit sessions per user
			const validUserSessions = this.activeSessions.safeEntries()
				.filter(([_, session]) => session.userId === userId)
				.sort((a, b) => b[1].expiresAt - a[1].expiresAt);

			if (validUserSessions.length >= this.MAX_SESSIONS_PER_USER) {
				// Remove oldest sessions
				const toRemove = validUserSessions.slice(this.MAX_SESSIONS_PER_USER - 1);
				for (const [token] of toRemove) {
					try {
						await this.activeSessions.safeDelete(token);
					} catch (error) {
						console.warn(`Failed to delete old session ${token}:`, error.message);
					}
				}
			}
		} catch (error) {
			console.error('Failed to cleanup user sessions:', error.message);
		}
	}

	/**
	 * Revoke a session
	 */
	public static async revokeSession(sessionToken: string): Promise<boolean> {
		try {
			return await this.activeSessions.safeDelete(sessionToken);
		} catch (error) {
			console.error('Failed to revoke session:', error.message);
			return false;
		}
	}

	/**
	 * Revoke all sessions for a user
	 */
	public static async revokeUserSessions(userId: string): Promise<number> {
		let revokedCount = 0;
		try {
			const entries = this.activeSessions.safeEntries();
			for (const [token, session] of entries) {
				if (session.userId === userId) {
					try {
						await this.activeSessions.safeDelete(token);
						revokedCount++;
					} catch (error) {
						console.warn(`Failed to revoke session ${token}:`, error.message);
					}
				}
			}
		} catch (error) {
			console.error('Failed to revoke user sessions:', error.message);
		}
		return revokedCount;
	}

	/**
	 * Get statistics about active sessions
	 */
	public static getSessionStats(): {
		activeSessions: number;
		uniqueUsers: number;
		expiringSoon: number;
	} {
		const now = Date.now();
		const fiveMinutes = 5 * 60 * 1000;
		
		const uniqueUsers = new Set();
		let expiringSoon = 0;

		for (const session of this.activeSessions.safeValues()) {
			if (now < session.expiresAt) {
				uniqueUsers.add(session.userId);
				if (session.expiresAt - now < fiveMinutes) {
					expiringSoon++;
				}
			}
		}

		return {
			activeSessions: this.activeSessions.safeSize(),
			uniqueUsers: uniqueUsers.size,
			expiringSoon
		};
	}

	/**
	 * Initialize authentication system (setup default admin if needed)
	 */
	public static async initialize(): Promise<void> {
		try {
			// Ensure auth directory exists
			const authDir = '/var/n8n/auth';
			try {
				await fs.promises.access(authDir);
			} catch {
				await fs.promises.mkdir(authDir, { recursive: true, mode: 0o700 });
			}

			// Check if any admin exists
			const files = await fs.promises.readdir(authDir);
			const hasAdmin = await Promise.all(files.map(async file => {
				try {
					const credPath = path.join(authDir, file);
					const creds = JSON.parse(await fs.promises.readFile(credPath, 'utf8'));
					return creds.permissions && creds.permissions.includes('admin');
				} catch {
					return false;
				}
			})).then(results => results.some(Boolean));

			// Create default admin if none exists
			if (!hasAdmin) {
				const defaultAdminId = 'system_admin';
				const defaultPermissions = ['admin'];
				
				const { apiKey } = await this.createUserCredentials(defaultAdminId, defaultPermissions);
				
				console.log(`Created default admin credentials:`);
				console.log(`User ID: ${defaultAdminId}`);
				console.log(`API Key: ${apiKey}`);
				console.log(`Permissions: ${defaultPermissions.join(', ')}`);
				console.log(`Store these credentials securely!`);
			}

			// Schedule periodic cleanup
			resourceManager.createInterval(async () => {
				await this.performPeriodicCleanup();
			}, 10 * 60 * 1000, 'Authentication periodic cleanup');

		} catch (error) {
			console.warn('Failed to initialize authentication system:', error.message);
		}
	}

	/**
	 * Perform periodic cleanup of expired sessions and cache
	 */
	private static async performPeriodicCleanup(): Promise<void> {
		const now = Date.now();

		try {
			// Clean expired sessions
			const sessionEntries = this.activeSessions.safeEntries();
			for (const [token, session] of sessionEntries) {
				if (now > session.expiresAt) {
					try {
						await this.activeSessions.safeDelete(token);
					} catch (error) {
						console.warn(`Failed to delete expired session ${token}:`, error.message);
					}
				}
			}

			// Clean expired cache entries
			const cacheEntries = this.authCache.safeEntries();
			for (const [key, cached] of cacheEntries) {
				if (now - cached.cachedAt > this.AUTH_CACHE_DURATION) {
					try {
						await this.authCache.safeDelete(key);
					} catch (error) {
						console.warn(`Failed to delete expired cache entry ${key}:`, error.message);
					}
				}
			}
		} catch (error) {
			console.error('Failed to perform periodic cleanup:', error.message);
		}
	}
}