// Simple SENIAT proxy API for RifModal
// Endpoints:
//  GET  /api/rif/session  -> returns { ok, sessionId, dataUrl }
//  POST /api/rif/verify   -> body { rif, codigo, sessionId?, includeHtml? } returns { ok, rifData, html? }

const express = require('express');
const cors = require('cors');
const cheerio = require('cheerio');
const { JSDOM } = require('jsdom');

// node-fetch v3 ESM support (dynamic import fallback)
let fetch;
try {
	fetch = require('node-fetch');
	if (fetch && fetch.default) fetch = fetch.default;
} catch (_) {
	fetch = (...args) => import('node-fetch').then(m => m.default(...args));
}

const app = express();
app.use(cors());
app.use(express.json({ limit: '1mb' }));

const BASE = 'http://contribuyente.seniat.gob.ve/BuscaRif';

// In-memory session store: sessionId -> cookie header
const sessions = new Map();
// Último resultado verificado (para debug visual)
let lastResult = null;

function makeCookieHeader(setCookieArray) {
	if (!setCookieArray) return '';
	const arr = Array.isArray(setCookieArray) ? setCookieArray : [setCookieArray];
	return arr.map(s => s.split(';')[0]).join('; ');
}

function normalizeRifValue(raw) {
	const value = String(raw || '').trim().toUpperCase();
	if (!value) return null;
	const letter = value.charAt(0);
	const digits = value.slice(1).replace(/\D/g, '');
	if (!letter || !digits) return null;
	return `${letter}${digits.padStart(9, '0')}`;
}

// Parsing helpers (copied from CLI for parity)
function extractRetencionPercent(text) {
	if (!text) return null;
	const candidates = [];
	const m1 = text.match(/retención\s+del\s*(\d{1,3})\s*%/i);
	if (m1 && m1[1]) candidates.push(`${m1[1]}%`);
	const norm = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
	const m2 = norm.match(/retencion\s+del\s*(\d{1,3})\s*%/i);
	if (m2 && m2[1]) candidates.push(`${m2[1]}%`);
	const m3 = norm.match(/retencion[^%]*?(\d{1,3})\s*%/i);
	if (m3 && m3[1]) candidates.push(`${m3[1]}%`);
	const m4 = norm.match(/(\d{1,3})\s*%/);
	if (m4 && m4[1]) candidates.push(`${m4[1]}%`);
	return candidates.length ? candidates[0] : null;
}

function extractActividadCondicion(text) {
	if (!text) return { actividad_economica: null, condicion_iva: null };
	const flat = String(text).replace(/<br\s*\/?>(\s)*/gi, ' ').replace(/&nbsp;/gi, ' ').replace(/\s\s+/g, ' ').trim();
	const norm = flat.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
	let actividad = null;
	// Actividad: soporta 'Condición del IVA:' como delimitador
	let mAct = flat.match(/Actividad\s+Económica\s*:?[\s\u00A0]*([^]*?)\s*Condición(?:\s+(?:del\s+)?IVA)?\s*:/i);
	if (!(mAct && mAct[1])) mAct = norm.match(/Actividad\s+Economica\s*:?[\s\u00A0]*([^]*?)\s*Condicion(?:\s+(?:del\s+)?IVA)?\s*:/i);
	if (mAct && mAct[1]) actividad = mAct[1].trim();
	if (actividad && actividad.toUpperCase() === 'INFORMACION NO DISPONIBLE') actividad = null;
	let condicion = null;
	// Condición: soporta 'Condición del IVA:' y fin de bloque si no hay leyenda
	let mCond = flat.match(/Condición(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La\s+condición\s+de\s+este\s+contribuyente|$)/i);
	if (!(mCond && mCond[1])) mCond = norm.match(/Condicion(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La\s+condicion\s+de\s+este\s+contribuyente|$)/i);
	if (mCond && mCond[1]) condicion = mCond[1].trim().replace(/\s+/g, ' ');
	return { actividad_economica: actividad, condicion_iva: condicion };
}

function extractWithDomScript(html) {
	const dom = new JSDOM(html);
	const { document } = dom.window;
	try {
		function extraerDatosRIF_Limpio_fromDOM(document) {
			const datosRIF = {
				rif: 'NO ENCONTRADO',
				nombre: 'NO ENCONTRADO',
				actividad_economica: 'NO ENCONTRADO',
				condicion_iva: 'NO ENCONTRADO',
				retencion_iva: '0%'
			};
			const elementoRIFNombre = document.querySelector('table[align="center"] font[face="Verdana"][size="2"]');
			if (elementoRIFNombre) {
				const textoCompleto = elementoRIFNombre.textContent.trim();
				const match = textoCompleto.match(/^(\w+)\s+(.+)$/);
				if (match && match.length === 3) {
					datosRIF.rif = match[1];
					datosRIF.nombre = match[2];
				}
			}
			const elementoDetalleTable = document.querySelectorAll('table')[2];
			const elementoDetalle = elementoDetalleTable ? elementoDetalleTable.querySelector('font[face="Verdana"][size="1"]') : null;
			if (elementoDetalle) {
				let textoLimpio = elementoDetalle.innerHTML
					.replace(/<br>/gi, ' ')
					.replace(/&nbsp;/gi, ' ')
					.replace(/\s\s+/g, ' ')
					.trim();
				let matchActividad = textoLimpio.match(/Actividad Económica:\s*(.*?)\s*Condición:/i);
				if (!(matchActividad && matchActividad[1])) {
					const norm = textoLimpio.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
					matchActividad = norm.match(/Actividad Economica:\s*(.*?)\s*Condicion:/i);
					if (matchActividad && matchActividad[1]) textoLimpio = norm;
				}
				if (matchActividad && matchActividad[1] && matchActividad[1].trim().toUpperCase() !== 'INFORMACION NO DISPONIBLE') {
					datosRIF.actividad_economica = matchActividad[1].trim();
				}
				let matchCondicion = textoLimpio.match(/Condición:\s*(.*?)(?:La condición de este contribuyente|$)/i);
				if (!(matchCondicion && matchCondicion[1])) {
					const norm = textoLimpio.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
					matchCondicion = norm.match(/Condicion:\s*(.*?)(?:La condicion de este contribuyente|$)/i);
					if (matchCondicion && matchCondicion[1]) textoLimpio = norm;
				}
				if (matchCondicion && matchCondicion[1]) {
					datosRIF.condicion_iva = matchCondicion[1].trim().replace(/\s+/g, ' ');
				}
				const ret = extractRetencionPercent(textoLimpio);
				if (ret) datosRIF.retencion_iva = ret;
			}
			if (!datosRIF.actividad_economica || !datosRIF.condicion_iva) {
				const bodyHTML = (document.body && document.body.innerHTML) ? document.body.innerHTML : document.documentElement.innerHTML;
				const { actividad_economica, condicion_iva } = extractActividadCondicion(bodyHTML);
				if (!datosRIF.actividad_economica) datosRIF.actividad_economica = actividad_economica;
				if (!datosRIF.condicion_iva) datosRIF.condicion_iva = condicion_iva;
			}
			return datosRIF;
		}
		return extraerDatosRIF_Limpio_fromDOM(document);
	} catch (_) {
		return null;
	}
}

function extractRifDetails(html) {
	const $ = cheerio.load(html);
	try {
		let rif = null;
		let nombre = null;
		let nameText = ($('table[align="center"] font[face="Verdana"][size="2"]').first().text() || '')
			.replace(/\s+/g, ' ')
			.trim();
		if (!nameText) {
			const tNameFallback = $('form#consulta').nextAll('table').eq(0);
			nameText = (tNameFallback.find('font[face="Verdana"][size="2"]').first().text() || '')
				.replace(/\s+/g, ' ')
				.trim();
		}
		const m = nameText.match(/^([VEJGCP]\d{8,})\s+(.+)$/i);
		if (m) {
			rif = m[1];
			nombre = m[2];
		}

		let fontDetail = $('table').eq(2).find('font[face="Verdana"][size="1"]').first();
		if (!fontDetail || fontDetail.length === 0) {
			const candidates = $('font[face="Verdana"][size="1"]').toArray();
			fontDetail = null;
			for (const el of candidates) {
				const raw = $(el).html() || $(el).text() || '';
				const rawNorm = raw.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
				if (rawNorm.includes('actividad economica') || rawNorm.includes('condicion')) {
					fontDetail = $(el);
					break;
				}
			}
		}

		let actividad_economica = null;
		let condicion_iva = null;
		let retencion_iva = null;
		if (fontDetail && fontDetail.length) {
			const detailHtml = fontDetail.html() || '';
			const limpio = detailHtml
				.replace(/<br\s*\/?>(\s)*/gi, ' ')
				.replace(/&nbsp;/gi, ' ')
				.replace(/\s\s+/g, ' ')
				.trim();
			const norm = limpio.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
			const actividadMatch = norm.match(/Actividad\s+Economica\s*:?[\s\u00A0]*([^]*?)\s*Condicion(?:\s+(?:del\s+)?IVA)?\s*:/i);
			const condicionMatch = norm.match(/Condicion(?:\s+(?:del\s+)?IVA)?\s*:?[\s\u00A0]*(.*?)(?:La\s+condicion\s+de\s+este\s+contribuyente|$)/i);
			const retencion = extractRetencionPercent(limpio) || extractRetencionPercent(norm);
			const actividadTmp = actividadMatch && actividadMatch[1] ? actividadMatch[1].trim() : null;
			if (actividadTmp && actividadTmp.toUpperCase() !== 'INFORMACION NO DISPONIBLE') actividad_economica = actividadTmp;
			condicion_iva = condicionMatch && condicionMatch[1] ? condicionMatch[1].trim() : null;
			retencion_iva = retencion || null;
		}
		if (!actividad_economica || !condicion_iva) {
			const wholeHTML = $.root().html() || '';
			const ac = extractActividadCondicion(wholeHTML);
			if (!actividad_economica) actividad_economica = ac.actividad_economica;
			if (!condicion_iva) condicion_iva = ac.condicion_iva;
		}
		const hasAny = rif || nombre || actividad_economica || condicion_iva || retencion_iva;
		if (!hasAny) return null;
		return { rif, nombre, actividad_economica, condicion_iva, retencion_iva };
	} catch (_) {
		return null;
	}
}

// Detecta mensaje de captcha inválido en la respuesta del SENIAT
function detectBadCaptcha(html) {
	if (!html) return false;
	try {
		const plain = String(html)
			.replace(/<script[\s\S]*?<\/script>/gi, ' ')
			.replace(/<style[\s\S]*?<\/style>/gi, ' ')
			.replace(/<[^>]+>/g, ' ')
			.replace(/\s+/g, ' ')
			.trim();
		const norm = plain.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
		// Patrones amplios: "codigo ... incorrect|inval|no coincide|erroneo|equivocado" o menciones de captcha
		if (/captcha/.test(norm)) return true;
		if (/(codigo|codig)[^a-z0-9]{0,10}(seguridad)?[^a-z0-9]{0,20}(incorrect|inval|no\s+coincide|erroneo|equivocado)/.test(norm)) return true;
	} catch (_) {}
	return false;
}

// Routes
app.get('/api/rif/session', async (req, res) => {
	try {
		const pageRes = await fetch(`${BASE}/BuscaRif.jsp`, { method: 'GET' });
		if (!pageRes.ok) return res.status(502).json({ ok: false, message: 'No se pudo iniciar sesión' });
		const setCookie = pageRes.headers.raw ? pageRes.headers.raw()['set-cookie'] : pageRes.headers.get('set-cookie');
		const cookieHeader = makeCookieHeader(setCookie);
		// Create sessionId
		const sessionId = String(Date.now()) + Math.random().toString(36).slice(2, 8);
		if (cookieHeader) sessions.set(sessionId, cookieHeader);
		const rc = await fetch(`${BASE}/Captcha.jpg`, { method: 'GET', headers: { Cookie: cookieHeader } });
		if (!rc.ok) return res.status(502).json({ ok: false, message: 'No se pudo obtener captcha' });
		const buf = await rc.buffer();
		const dataUrl = `data:image/jpeg;base64,${buf.toString('base64')}`;
		return res.json({ ok: true, sessionId, dataUrl });
	} catch (err) {
		return res.status(500).json({ ok: false, message: 'Error de servidor' });
	}
});

app.post('/api/rif/verify', async (req, res) => {
	try {
		const { rif, codigo, sessionId, includeHtml } = req.body || {};
		const normalizedRif = normalizeRifValue(rif);
		const normalizedCodigo = String(codigo || '').trim();
		if (!normalizedRif) return res.status(400).json({ ok: false, message: 'RIF inválido' });
		if (!normalizedCodigo) return res.status(400).json({ ok: false, message: 'Código captcha requerido' });
		const cookie = sessionId && sessions.get(sessionId) ? sessions.get(sessionId) : '';
		const form = new URLSearchParams();
		form.append('p_rif', normalizedRif);
		form.append('codigo', normalizedCodigo);
		form.append('busca', ' Buscar ');
		const r = await fetch(`${BASE}/BuscaRif.jsp`, {
			method: 'POST',
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded',
				'Cookie': cookie,
				'Referer': `${BASE}/BuscaRif.jsp`
			},
			body: form.toString()
		});
		if (!r.ok) return res.status(502).json({ ok: false, message: 'Error del servidor SENIAT' });
		const html = await r.text();
			// Si el captcha fue incorrecto, informar explícitamente para que el cliente recargue uno nuevo
			if (detectBadCaptcha(html)) {
				return res.status(400).json({ ok: false, code: 'BAD_CAPTCHA', message: 'Código de seguridad (captcha) incorrecto. Inténtalo nuevamente.' });
			}
		const rifDataCheerio = extractRifDetails(html) || {};
		const rifDataDom = extractWithDomScript(html) || {};
		const rifData = Object.assign({}, rifDataCheerio, rifDataDom);
		// Log básico del resultado en consola del servidor
		try {
			console.log('RIF verificado:', rifData.rif || '-', '-', rifData.nombre || '-');
			if (console.table) console.table(rifData);
		} catch (_) {}
		// Guardar último resultado para debug visual
		lastResult = {
			at: new Date().toISOString(),
			rifData,
			hasHtml: !!includeHtml,
			html: includeHtml ? html : undefined
		};
		const response = { ok: true, rifData };
		if (includeHtml) response.html = html;
		return res.json(response);
	} catch (err) {
		return res.status(500).json({ ok: false, message: 'Error al verificar' });
	}
});

app.get('/', (req, res) => {
	res.json({ ok: true, message: 'RIF proxy activo' });
});

// Página simple para visualizar el último resultado en el servidor (útil para debug)
app.get('/debug/last', (req, res) => {
	res.setHeader('Content-Type', 'text/html; charset=utf-8');
	if (!lastResult) {
		return res.end(`<!doctype html>
		<html><head><meta charset="utf-8"><title>Último resultado</title>
		<style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;padding:24px;color:#222} .card{border:1px solid #ddd;border-radius:8px;padding:16px;max-width:720px} .row{margin:6px 0} .key{font-weight:600;width:200px;display:inline-block} pre{white-space:pre-wrap;word-break:break-word;background:#f9fafb;border:1px solid #eee;border-radius:6px;padding:12px;max-height:320px;overflow:auto}</style>
		</head><body>
		<h2>Último resultado</h2>
		<div class="card">
			<div class="row">Aún no hay resultados procesados.</div>
		</div>
		</body></html>`);
	}
	const d = lastResult.rifData || {};
	const fields = [
		['RIF', d.rif || '-'],
		['Nombre', d.nombre || '-'],
		['Actividad Económica', d.actividad_economica || '-'],
		['Condición IVA', d.condicion_iva || '-'],
		['Retención IVA', d.retencion_iva || '-'],
		['Fecha', lastResult.at]
	];
	const htmlBlock = lastResult.hasHtml && lastResult.html
		? `<details style="margin-top:10px"><summary>Ver HTML de la respuesta</summary><pre>${escapeHtml(lastResult.html)}</pre></details>`
		: '<div style="margin-top:10px;color:#666">(HTML no almacenado; envía includeHtml: true en /api/rif/verify para guardarlo)</div>';
	const page = `<!doctype html>
	<html><head><meta charset="utf-8"><title>Último resultado</title>
	<style>body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;padding:24px;color:#222} .card{border:1px solid #ddd;border-radius:8px;padding:16px;max-width:860px} .row{margin:6px 0} .key{font-weight:600;width:220px;display:inline-block}</style>
	</head><body>
	<h2>Último resultado (servidor)</h2>
	<div class="card">
		${fields.map(([k,v]) => `<div class="row"><span class="key">${k}:</span> <span>${escapeHtml(String(v))}</span></div>`).join('')}
		${htmlBlock}
	</div>
	</body></html>`;
	res.end(page);
});

// JSON del último resultado
app.get('/debug/last.json', (req, res) => {
	res.json(lastResult || { ok: false, message: 'No hay resultado' });
});

function escapeHtml(str) {
	return str
		.replace(/&/g, '&amp;')
		.replace(/</g, '&lt;')
		.replace(/>/g, '&gt;')
		.replace(/"/g, '&quot;')
		.replace(/'/g, '&#39;');
}

const PORT = process.env.PORT || 4000;
const HOST = process.env.HOST || '0.0.0.0'; // escuchar en todas las interfaces para acceso LAN
app.listen(PORT, HOST, () => {
	console.log(`RIF proxy escuchando en http://${HOST}:${PORT}`);
});

