import { REQUIRED_ENV } from '../env/required';

type WebhookConfig = {
	url: string;
	enabled: boolean;
	webhook_by_events: boolean;
	webhook_base64: boolean;
	events: string[];
};

type WebhookResponse = {
	webhook: {
		instanceName: string;
		webhook: {
			url: string;
			events: string[];
			enabled: boolean;
		};
	};
};

export class WebhookManager {
	private static readonly REQUIRED_EVENTS = [
		'APPLICATION_STARTUP',
		'MESSAGES_UPSERT',
		'GROUPS_UPSERT',
		'MESSAGES_UPDATE',
		'MESSAGES_DELETE',
		'PRESENCE_UPDATE',
		'CONTACTS_UPDATE',
		'CHATS_UPDATE'
	];

	private static validateConfig() {
		// Solo requerimos aquí las vars necesarias para registrar/verificar el webhook.
		// Excluimos CHATBOT_PHONE_NUMBER para que los tests unitarios de validación no fallen.
		const missing = REQUIRED_ENV
			.filter(v => v !== 'CHATBOT_PHONE_NUMBER')
			.filter(v => !process.env[v]);
		if (missing.length) {
			throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
		}

		if (!process.env.WEBHOOK_URL?.trim()) {
			throw new Error('WEBHOOK_URL environment variable is required and cannot be empty');
		}

		try {
			const url = new URL(process.env.WEBHOOK_URL);
			
			// Enhanced internal Docker URL validation
			if (process.env.WEBHOOK_URL.startsWith('http://srv-captain--')) {
				if (!url.port) {
					console.warn('Internal Docker URL missing port number - this may cause connection issues');
				}
			}
			
			// Allow internal docker URLs in production
			if (process.env.NODE_ENV === 'production') {
				const isOk = (url.protocol === 'http:' || url.protocol === 'https:') || process.env.WEBHOOK_URL.startsWith('http://srv-captain--');
				if (!isOk) {
					console.warn('Production WEBHOOK_URL should use http/https or internal docker URL');
				}
			} else if (!(url.protocol === 'http:' || url.protocol === 'https:')) {
				throw new Error('WEBHOOK_URL must use http or https protocol');
			}
		} catch (e) {
			throw new Error(`Invalid WEBHOOK_URL: ${e.message}`);
		}
	}

	private static getConfig(): { webhook: WebhookConfig } {
		const fromEnv = String(process.env.WEBHOOK_EVENTS || '')
			.split(',')
			.map(s => s.trim())
			.filter(Boolean);
		const allow = [
			'APPLICATION_STARTUP',
			'MESSAGES_UPSERT',
			'GROUPS_UPSERT',
			'MESSAGES_UPDATE',
			'MESSAGES_DELETE',
			'PRESENCE_UPDATE',
			'CONTACTS_UPDATE',
			'CHATS_UPDATE'
		];
		const events = (fromEnv.length ? fromEnv : ['APPLICATION_STARTUP','MESSAGES_UPSERT','GROUPS_UPSERT'])
			.filter(e => allow.includes(e));
		return {
			webhook: {
				url: process.env.WEBHOOK_URL!,
				enabled: true,
				webhook_by_events: true,
				webhook_base64: true,
				events
			}
		};
	}

	private static getApiUrl(): string {
		return `${process.env.EVOLUTION_API_URL}/webhook/set/${process.env.EVOLUTION_API_INSTANCE}`;
	}

	private static getHeaders(): HeadersInit {
		return {
			apikey: process.env.EVOLUTION_API_KEY!,
			'Content-Type': 'application/json',
		};
	}

	static async registerWebhook(): Promise<WebhookResponse> {
		this.validateConfig();

		// First test if our own endpoint is reachable
		const endpointTest = await this.testWebhookEndpoint();
		if (!endpointTest) {
			throw new Error('Webhook endpoint test failed - check your server logs');
		}

		const config = this.getConfig();
		const apiUrl = this.getApiUrl();

		const logSafeUrl = (url: string) => url ? `${url.substring(0, 20)}...` : 'invalid-url';

		console.log('ℹ️ Attempting to register webhook:', {
			apiUrl,
			config: {
				...config,
				webhook: {
					...config.webhook,
					url: logSafeUrl(config.webhook.url),
					events: config.webhook.events
				}
			}
		});

		const response = await fetch(apiUrl, {
			method: 'POST',
			headers: this.getHeaders(),
			body: JSON.stringify(config),
		});

		if (!response.ok) {
			let errorDetails = response.statusText;
			try {
				const errorBody = await response.text();
				errorDetails += ` - ${errorBody}`;
			} catch { }
			throw new Error(`Failed to register webhook: ${errorDetails}`);
		}

		const data = await response.json();

		if (!data?.enabled) {
			throw new Error('Webhook registration failed - not enabled in response');
		}

		console.log('✅ Webhook successfully registered:', {
			url: data.url,
			events: data.events,
			id: data.id
		});

		return data;
	}

	static async verifyWebhook(): Promise<boolean> {
		try {
			const url = `${process.env.EVOLUTION_API_URL}/webhook/find/${process.env.EVOLUTION_API_INSTANCE}`;
			console.log('ℹ️ Verifying webhook at:', url);
			
			const response = await fetch(url, {
				method: 'GET',
				headers: this.getHeaders(),
			});

			if (!response.ok) {
				const body = await response.text();
				console.error('❌ Webhook verification failed:', {
					status: response.status,
					statusText: response.statusText,
					body
				});
				return false;
			}

			const data = await response.json();
			console.log('ℹ️ Webhook verification response:', data);
			
			const isEnabled = data?.enabled === true;
			if (!isEnabled) {
				console.error('❌ Webhook not enabled in verification response');
			}
			return isEnabled;
		} catch (error) {
			console.error('❌ Webhook verification error:', error instanceof Error ? error.stack : error);
			return false;
		}
	}

	static async testWebhookEndpoint(): Promise<boolean> {
		try {
			if (!process.env.WEBHOOK_URL) {
				throw new Error('WEBHOOK_URL is not set');
			}

			// Skip self-test in production if using internal URL
			if (process.env.NODE_ENV === 'production' && 
				process.env.WEBHOOK_URL.startsWith('http://srv-captain--')) {
				console.log('ℹ️ Skipping self-test for internal production URL');
				return true;
			}

			console.log('ℹ️ Testing webhook endpoint:', process.env.WEBHOOK_URL);
			
			const testPayload = {
				test: true,
				timestamp: new Date().toISOString()
			};

			const response = await fetch(process.env.WEBHOOK_URL, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
				},
				body: JSON.stringify(testPayload),
				timeout: 5000 // Add timeout to prevent hanging
			});

			if (!response.ok) {
				const body = await response.text();
				console.error('❌ Webhook test failed:', {
					status: response.status,
					statusText: response.statusText,
					body
				});
				return false;
			}

			console.log('✅ Webhook endpoint test successful');
			return true;
		} catch (error) {
			console.error('❌ Webhook test error:', error instanceof Error ? error.stack : error);
			return false;
		}
	}

	// ===== Auto-ensure loop con reintentos y backoff =====

	private static autoEnsureTimer: any = null;
	private static autoEnsureActive: boolean = false;
	private static autoEnsureAttempts: number = 0;

	private static getRetryConfig() {
		const n = (v: any, d: number) => {
			const x = Number(v);
			return Number.isFinite(x) && x >= 0 ? x : d;
		};
		const initial = n(process.env.WEBHOOK_RETRY_INITIAL_DELAY_MS, 1000);
		const max = Math.max(initial, n(process.env.WEBHOOK_RETRY_MAX_DELAY_MS, 60000));
		const degraded = Math.max(max, n(process.env.WEBHOOK_RETRY_DEGRADED_INTERVAL_MS, 300000));
		const jitter = String(process.env.WEBHOOK_RETRY_JITTER || 'true').toLowerCase();
		return {
			initialDelayMs: initial,
			maxDelayMs: max,
			degradedIntervalMs: degraded,
			jitter: ['true', '1', 'yes', 'on'].includes(jitter),
			maxAttempts: Number.isFinite(Number(process.env.WEBHOOK_RETRY_MAX_ATTEMPTS))
				? Math.max(0, Number(process.env.WEBHOOK_RETRY_MAX_ATTEMPTS))
				: Infinity
		};
	}

	private static jitter(val: number, enabled: boolean): number {
		if (!enabled) return val;
		const factor = 0.5 + Math.random(); // 0.5x - 1.5x
		return Math.max(1, Math.floor(val * factor));
	}

	private static nextDelayMs(attempt: number, cfg: ReturnType<typeof this.getRetryConfig>): number {
		// attempt 0 -> initial, 1 -> 2x, etc. capped at max, luego degradado cada degradedIntervalMs
		const pow = Math.min(cfg.maxDelayMs, cfg.initialDelayMs * Math.pow(2, attempt));
		return this.jitter(pow, cfg.jitter);
	}

	static startAutoEnsure(): void {
		if (this.autoEnsureActive) {
			return;
		}
		this.autoEnsureActive = true;
		this.autoEnsureAttempts = 0;

		const cfg = this.getRetryConfig();

		const attemptOnce = async () => {
			// Validación de config: si falta algo, abortar sin reintentos
			try {
				this.validateConfig();
			} catch (e) {
				console.error('❌ Webhook auto-ensure aborted due to invalid configuration:', e instanceof Error ? e.message : e);
				this.stopAutoEnsure();
				return;
			}

			try {
				await this.registerWebhook();
				const ok = await this.verifyWebhook();
				if (ok) {
					console.log('✅ Webhook is active');
					this.stopAutoEnsure();
					return;
				}
				throw new Error('verify returned false');
			} catch (err) {
				if (!this.autoEnsureActive) return; // pudo ser parado durante espera
				const a = this.autoEnsureAttempts++;
				const delay = a < 10 ? this.nextDelayMs(a, cfg) : cfg.degradedIntervalMs;
				const msg = err instanceof Error ? `${err.message}` : String(err);
				console.warn(`⚠️ Webhook ensure attempt #${a + 1} failed: ${msg}. Retrying in ${delay}ms`);
				this.autoEnsureTimer = setTimeout(() => {
					// Evitar múltiples overlapped
					if (!this.autoEnsureActive) return;
					attemptOnce();
				}, delay);
			}
		};

		// Disparar el primer intento sin esperar
		attemptOnce();
	}

	static stopAutoEnsure(): void {
		if (this.autoEnsureTimer) {
			try { clearTimeout(this.autoEnsureTimer); } catch {}
			this.autoEnsureTimer = null;
		}
		this.autoEnsureActive = false;
	}
}
