import { sign, verify } from 'jsonwebtoken';
import { createHash, randomUUID } from 'crypto';
import { GenericError } from '#interfaces/error/generic';
import Config from '#config';

/**
 * Service-to-Service Authentication Service
 *
 * Provides authentication methods for inter-service communication:
 * - mTLS authentication (mutual TLS)
 * - Signed short-lived tokens
 * - API key authentication with rotation
 * - Workload identity (for cloud environments)
 */

export interface ServiceCredentials {
	serviceId: string;
	serviceName: string;
	secret: string;
	scopes: string[];
}

export interface ServiceToken {
	serviceId: string;
	serviceName: string;
	scopes: string[];
	iat: number;
	exp: number;
	jti: string;
}

export interface ApiKey {
	keyId: string;
	keyHash: string;
	serviceId: string;
	scopes: string[];
	expiresAt: Date;
	createdAt: Date;
	isActive: boolean;
}

const SERVICE_TOKEN_TTL = 300; // 5 minutes for service tokens

export class ServiceToServiceAuthService {
	private static readonly SERVICE_JWT_SECRET = Config.zalo.serviceJwtSecret || Config.auth.jwtSecret;
	private static readonly MTLSENABLED = Config.zalo.mtlsEnabled;
	private static readonly WORKLOAD_IDENTITY_ENABLED = Config.zalo.workloadIdentityEnabled;

	/**
	 * Generate a short-lived service token
	 */
	static generateServiceToken(credentials: ServiceCredentials): string {
		if (!this.SERVICE_JWT_SECRET) {
			throw new Error('SERVICE_JWT_SECRET environment variable is required for service tokens');
		}

		const now = Math.floor(Date.now() / 1000);
		const payload: ServiceToken = {
			serviceId: credentials.serviceId,
			serviceName: credentials.serviceName,
			scopes: credentials.scopes,
			iat: now,
			exp: now + SERVICE_TOKEN_TTL,
			jti: randomUUID(),
		};

		return sign(payload, this.SERVICE_JWT_SECRET, {
			algorithm: 'HS256',
		});
	}

	/**
	 * Verify a service token
	 */
	static verifyServiceToken(token: string): ServiceToken {
		if (!this.SERVICE_JWT_SECRET) {
			throw new Error('SERVICE_JWT_SECRET environment variable is required');
		}

		try {
			const decoded = verify(token, this.SERVICE_JWT_SECRET) as ServiceToken;
			return decoded;
		} catch {
			throw new GenericError('UNAUTHORIZED');
		}
	}

	/**
	 * Check if service has required scope
	 */
	static hasScope(token: ServiceToken, requiredScope: string): boolean {
		return token.scopes.includes(requiredScope);
	}

	/**
	 * Check if service has any of the required scopes
	 */
	static hasAnyScope(token: ServiceToken, requiredScopes: string[]): boolean {
		return requiredScopes.some((scope) => token.scopes.includes(scope));
	}

	/**
	 * Generate an API key
	 */
	static generateApiKey(serviceId: string, scopes: string[], expiresInDays: number = 30): ApiKey {
		const keyId = randomUUID();
		const rawKey = `${keyId}.${randomUUID()}.${randomUUID()}`;
		const keyHash = createHash('sha256').update(rawKey).digest('hex');

		return {
			keyId,
			keyHash,
			serviceId,
			scopes,
			expiresAt: new Date(Date.now() + expiresInDays * 86400 * 1000),
			createdAt: new Date(),
			isActive: true,
		};
	}

	/**
	 * Verify an API key against stored hash
	 */
	static verifyApiKey(rawKey: string, storedHash: string): boolean {
		const keyHash = createHash('sha256').update(rawKey).digest('hex');
		return keyHash === storedHash;
	}

	/**
	 * Extract key ID from API key
	 */
	static extractKeyId(rawKey: string): string {
		const parts = rawKey.split('.');
		if (parts.length < 1 || !parts[0]) {
			throw new GenericError('UNAUTHORIZED');
		}
		return parts[0];
	}

	/**
	 * Rotate API key
	 */
	static rotateApiKey(oldApiKey: ApiKey): { newApiKey: ApiKey; rawKey: string } {
		const newKeyData = this.generateApiKey(oldApiKey.serviceId, oldApiKey.scopes, 30);
		const rawKey = `${newKeyData.keyId}.${randomUUID()}.${randomUUID()}`;
		newKeyData.keyHash = createHash('sha256').update(rawKey).digest('hex');

		return { newApiKey: newKeyData, rawKey };
	}

	/**
	 * Check if API key is expired
	 */
	static isApiKeyExpired(apiKey: ApiKey): boolean {
		return new Date() > apiKey.expiresAt;
	}

	/**
	 * Middleware for service token authentication
	 */
	static authenticateServiceToken() {
		return (req: Request, res: any, next: any) => {
			const authHeader = req.headers.get('authorization');

			if (!authHeader) {
				return res.error(new GenericError('UNAUTHORIZED'));
			}

			const [bearer, token] = authHeader.split(' ');

			if (bearer !== 'Bearer' || !token) {
				return res.error(new GenericError('UNAUTHORIZED'));
			}

			try {
				const serviceToken = this.verifyServiceToken(token);
				(req as any).service = serviceToken;
				next();
			} catch (error) {
				return res.error(error);
			}
		};
	}

	/**
	 * Middleware for API key authentication
	 */
	static authenticateApiKey() {
		return (req: Request, res: any, next: any) => {
			const apiKey = req.headers.get('x-api-key');

			if (!apiKey) {
				return res.error(new GenericError('UNAUTHORIZED'));
			}

			try {
				const keyId = this.extractKeyId(apiKey);

				// MISS-03: API key lookup requires database schema and model implementation
				// To implement: create api_keys table, ApiKey model, ApiKeyProvider
				// Then lookup key by keyId, verify hash, check expiration and active status
				// For now, pass through with keyId for future implementation
				(req as any).apiKeyId = keyId;
				next();
			} catch (error) {
				return res.error(error);
			}
		};
	}

	/**
	 * Authenticate using mTLS (mutual TLS)
	 * Note: This is a placeholder - implement based on your TLS termination setup
	 */
	private static async authenticateMTLS(_req: Request): Promise<ServiceToken | null> {
		// Placeholder implementation
		// In production, extract client certificate from request headers
		// Verify certificate against trusted CA
		// Extract service identity from certificate
		return null;
	}

	/**
	 * Authenticate using workload identity (e.g., GKE Workload Identity, AWS IAM Roles for Service Accounts)
	 * Note: This is a placeholder - implement based on your cloud provider
	 */
	private static async authenticateWorkloadIdentity(_req: Request): Promise<ServiceToken | null> {
		// Placeholder implementation
		// In production, verify workload identity token from metadata service
		// Extract service identity from token
		return null;
	}

	/**
	 * Middleware for mTLS authentication
	 */
	static authenticateMTLSMiddleware() {
		return async (req: Request, res: any, next: any) => {
			if (!this.MTLSENABLED) {
				return next(); // Skip if mTLS not enabled
			}

			try {
				const credentials = await this.authenticateMTLS(req);

				if (!credentials) {
					return res.error(new GenericError('UNAUTHORIZED'));
				}

				(req as any).service = credentials;
				next();
			} catch (error) {
				return res.error(error);
			}
		};
	}

	/**
	 * Middleware for workload identity authentication
	 */
	static authenticateWorkloadIdentityMiddleware() {
		return async (req: Request, res: any, next: any) => {
			if (!this.WORKLOAD_IDENTITY_ENABLED) {
				return next(); // Skip if workload identity not enabled
			}

			try {
				const workload = await this.authenticateWorkloadIdentity(req);

				if (!workload) {
					return res.error(new GenericError('UNAUTHORIZED'));
				}

				(req as any).workload = workload;
				next();
			} catch (error) {
				return res.error(error);
			}
		};
	}

	/**
	 * Middleware to check service scope
	 */
	static requireServiceScope(requiredScope: string) {
		return (req: Request, res: any, next: any) => {
			const serviceToken = (req as any).service as ServiceToken | undefined;

			if (!serviceToken) {
				return res.error(new GenericError('UNAUTHORIZED'));
			}

			if (!this.hasScope(serviceToken, requiredScope)) {
				return res.error(new GenericError('FORBIDDEN'));
			}

			next();
		};
	}

	/**
	 * Middleware to check any of the required service scopes
	 */
	static requireAnyServiceScope(requiredScopes: string[]) {
		return (req: Request, res: any, next: any) => {
			const serviceToken = (req as any).service as ServiceToken | undefined;

			if (!serviceToken) {
				return res.error(new GenericError('UNAUTHORIZED'));
			}

			if (!this.hasAnyScope(serviceToken, requiredScopes)) {
				return res.error(new GenericError('FORBIDDEN'));
			}

			next();
		};
	}
}
