import { IExecuteFunctions } from 'n8n-core';
import {
	INodeExecutionData,
	INodeType,
	INodeTypeDescription,
	IBinaryData,
} from 'n8n-workflow';
import { createWorker } from 'tesseract.js';
import { TextractClient, DetectDocumentTextCommand, AnalyzeDocumentCommand } from '@aws-sdk/client-textract';
import OpenAI from 'openai';
import axios from 'axios';
import { PDFExtract } from 'pdf.js-extract';
import { PDFDocument } from 'pdf-lib';

interface PdfPage {
	content: Array<{ str: string }>;
}

interface PdfExtractResult {
	pages: PdfPage[];
}

class OcrExtractor {
	private pdfExtract = new PDFExtract();

	async extractFromPdf(pdfBuffer: Buffer): Promise<string> {
		try {
			const data = await this.pdfExtract.extractBuffer(pdfBuffer) as PdfExtractResult;
			return data.pages.map(page => page.content.map(item => item.str).join(' ')).join('\n\n');
		} catch (error: any) {
			throw new Error(`Erro ao extrair texto do PDF: ${error.message}`);
		}
	}

	async validateAndProcessFile(binaryData: IBinaryData): Promise<{ type: string; data: Buffer }> {
		try {
			const mimeType = binaryData.mimeType || '';
			if (!binaryData.data) {
				throw new Error('Dados binários não encontrados');
			}
			
			const data = Buffer.from(binaryData.data, 'base64');
			if (!data || data.length === 0) {
				throw new Error('Falha ao converter dados base64 para buffer');
			}

			if (mimeType.startsWith('image/')) {
				return { type: 'image', data };
			} else if (mimeType === 'application/pdf') {
				return { type: 'pdf', data };
			} else {
				throw new Error(`Tipo de arquivo não suportado: ${mimeType}. Use PDF ou imagem.`);
			}
		} catch (error: any) {
			throw new Error(`Erro ao validar arquivo: ${error.message}`);
		}
	}

	async extractText(result: any, engine: string, fileType: string): Promise<string> {
		try {
			switch (engine) {
				case 'tesseract':
					if (fileType === 'pdf') {
						throw new Error('Tesseract.js não suporta PDF diretamente no n8n. Use OCR.space ou AWS Textract para PDFs.');
					}
					return result.text || '';
				case 'ocrspace':
					if (!result.ParsedResults || !result.ParsedResults[0]) {
						throw new Error('OCR.space não retornou resultados válidos');
					}
					return result.ParsedResults[0].ParsedText || '';
				case 'textract':
					if (!result.Blocks) {
						throw new Error('AWS Textract não retornou blocos de texto');
					}
					return result.Blocks
						.filter((block: any) => block.BlockType === 'LINE')
						.map((block: any) => block.Text)
						.join('\n') || '';
				default:
					throw new Error(`Engine "${engine}" não suportada`);
			}
		} catch (error: any) {
			throw new Error(`Erro ao extrair texto: ${error.message}`);
		}
	}

	async extractJson(result: any, engine: string, layout: any): Promise<any> {
		try {
			const text = await this.extractText(result, engine, 'image');
			if (Object.keys(layout).length === 0) {
				return { text };
			}

			const structuredData: any = {};
			for (const [field, coords] of Object.entries(layout)) {
				const { x, y, w, h } = coords as any;
				structuredData[field] = `Valor extraído para ${field}`;
			}
			return structuredData;
		} catch (error: any) {
			throw new Error(`Erro ao extrair JSON: ${error.message}`);
		}
	}

	async extractCsv(result: any, engine: string, layout: any): Promise<string> {
		try {
			const jsonData = await this.extractJson(result, engine, layout);
			if (Object.keys(layout).length === 0) {
				return jsonData.text;
			}

			const headers = Object.keys(jsonData);
			const values = Object.values(jsonData);
			return `${headers.join(',')}\n${values.join(',')}`;
		} catch (error: any) {
			throw new Error(`Erro ao extrair CSV: ${error.message}`);
		}
	}
}

async function analyzeWithAI(text: string, credentials: any): Promise<string> {
	try {
		const openai = new OpenAI({
			apiKey: credentials.apiKey,
		});

		const response = await openai.chat.completions.create({
			model: 'gpt-4',
			messages: [
				{
					role: 'system',
					content: 'Você é um assistente especializado em análise de texto extraído por OCR. Analise o texto fornecido e forneça um resumo estruturado com os principais pontos e informações relevantes.',
				},
				{
					role: 'user',
					content: text,
				},
			],
			temperature: 0.7,
		});

		return response.choices[0]?.message?.content || 'Não foi possível analisar o texto com IA.';
	} catch (error: any) {
		throw new Error(`Erro na análise com IA: ${error.message}`);
	}
}

export class CapivisionOcr implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'CAPIVISION OCR',
		name: 'capivisionOcr',
		icon: 'file:icon.svg',
		group: ['transform'],
		version: 1,
		description: 'OCR multiengine com visão apurada de capivara',
		defaults: {
			name: 'CAPIVISION OCR',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'ocrSpaceApi',
				required: true,
				displayOptions: {
					show: {
						engine: ['ocrspace'],
					},
				},
			},
			{
				name: 'awsTextractApi',
				required: true,
				displayOptions: {
					show: {
						engine: ['textract'],
					},
				},
			},
			{
				name: 'openAiApi',
				required: true,
				displayOptions: {
					show: {
						treatmentMethod: ['ocr_ai'],
					},
				},
			},
		],
		properties: [
			{
				displayName: 'Mecanismo OCR',
				name: 'engine',
				type: 'options',
				options: [
					{
						name: 'Tesseract.js (apenas imagens)',
						value: 'tesseract',
						description: 'Melhor para imagens simples e texto bem definido',
					},
					{
						name: 'OCR.space (imagens e PDF)',
						value: 'ocrspace',
						description: 'Suporta PDF e vários idiomas',
					},
					{
						name: 'AWS Textract (imagens e PDF)',
						value: 'textract',
						description: 'Melhor para documentos complexos e formulários',
					},
				],
				default: 'tesseract',
				required: true,
			},
			{
				displayName: 'Tipo de Entrada',
				name: 'imageFormat',
				type: 'options',
				options: [
					{
						name: 'Binário',
						value: 'binary',
					},
					{
						name: 'Base64',
						value: 'base64',
					},
				],
				default: 'binary',
				required: true,
			},
			{
				displayName: 'Input Binário',
				name: 'binaryPropertyName',
				type: 'string',
				default: 'data',
				required: true,
				displayOptions: {
					show: {
						imageFormat: ['binary'],
					},
				},
				description: 'Nome do campo que contém a imagem/PDF',
			},
			{
				displayName: 'String Base64',
				name: 'base64String',
				type: 'string',
				typeOptions: {
					rows: 4,
				},
				default: '',
				required: true,
				displayOptions: {
					show: {
						imageFormat: ['base64'],
					},
				},
				description: 'String base64 da imagem (pode incluir ou não o cabeçalho data:image)',
			},
			{
				displayName: 'Método de Tratamento',
				name: 'treatmentMethod',
				type: 'options',
				options: [
					{
						name: 'Apenas OCR (Sem IA)',
						value: 'ocr_only',
					},
					{
						name: 'OCR + IA',
						value: 'ocr_ai',
					},
				],
				default: 'ocr_only',
				required: true,
			},
			{
				displayName: 'Mecanismo IA',
				name: 'aiEngine',
				type: 'options',
				displayOptions: {
					show: {
						treatmentMethod: ['ocr_ai'],
					},
				},
				options: [
					{
						name: 'ChatGPT',
						value: 'chatgpt',
					},
				],
				default: 'chatgpt',
				required: true,
			},
			{
				displayName: 'Modelo IA',
				name: 'aiModel',
				type: 'options',
				displayOptions: {
					show: {
						treatmentMethod: ['ocr_ai'],
						aiEngine: ['chatgpt'],
					},
				},
				options: [
					{
						name: 'GPT-4o-mini',
						value: 'gpt-4-mini',
					},
					{
						name: 'GPT-4o',
						value: 'gpt-4',
					},
				],
				default: 'gpt-4-mini',
				required: true,
			},
			{
				displayName: 'Formato de Saída',
				name: 'outputFormat',
				type: 'options',
				options: [
					{
						name: 'Texto Puro',
						value: 'text',
					},
					{
						name: 'JSON Estruturado',
						value: 'json',
					},
					{
						name: 'CSV',
						value: 'csv',
					},
				],
				default: 'text',
				required: true,
			},
			{
				displayName: 'Preset de Layout (opcional)',
				name: 'layoutPreset',
				type: 'json',
				default: '{}',
				required: false,
				description: 'JSON com estrutura de coordenadas para extração',
			},
		],
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		try {
			const items = this.getInputData();
			const returnData: INodeExecutionData[] = [];
			const extractor = new OcrExtractor();

			for (let i = 0; i < items.length; i++) {
				let engine: string = '';
				let treatmentMethod: string = '';
				let outputFormat: string = '';
				let fileType: string = '';

				try {
					engine = this.getNodeParameter('engine', i) as string;
					outputFormat = this.getNodeParameter('outputFormat', i) as string;
					treatmentMethod = this.getNodeParameter('treatmentMethod', i) as string;
					const imageFormat = this.getNodeParameter('imageFormat', i) as string;

					let imageData: Buffer;
					let processedFile: { type: string; data: Buffer };

					if (imageFormat === 'binary') {
						const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
						const binaryData = items[i].binary;
						if (!binaryData) {
							throw new Error('Nenhum dado binário encontrado!');
						}
						const binaryProperty = binaryData[binaryPropertyName];
						if (!binaryProperty) {
							throw new Error(`Nenhum dado binário encontrado no campo "${binaryPropertyName}"!`);
						}
						processedFile = await extractor.validateAndProcessFile(binaryProperty);
						imageData = processedFile.data;
						fileType = processedFile.type;
					} else {
						const base64String = this.getNodeParameter('base64String', i) as string;
						if (!base64String) {
							throw new Error('String base64 não fornecida!');
						}
						const base64Clean = base64String.replace(/^data:image\/[a-zA-Z+]+;base64,/, '');
						imageData = Buffer.from(base64Clean, 'base64');
						fileType = 'image';
					}

					let extractedText: string = '';

					switch (engine) {
						case 'tesseract':
							if (fileType === 'pdf') {
								throw new Error('Tesseract.js não suporta PDF diretamente no n8n. Use OCR.space ou AWS Textract para PDFs.');
							}
							console.log('Iniciando processamento Tesseract...');
							console.log('Tamanho da imagem:', imageData.length, 'bytes');
							try {
								const worker = await createWorker({
									langPath: 'https://tessdata.projectnaptha.com/4.0.0',
									logger: m => console.log('Tesseract Log:', JSON.stringify(m)),
									errorHandler: e => console.error('Tesseract Error:', e),
								});
								console.log('Worker Tesseract criado');
								try {
									await worker.loadLanguage('por');
									console.log('Idioma carregado');
									await worker.initialize('por');
									console.log('Worker inicializado');
									const result = await worker.recognize(imageData);
									console.log('Reconhecimento concluído');
									if (!result || !result.data) {
										throw new Error('Resultado do Tesseract inválido');
									}
									extractedText = result.data.text || '';
									if (!extractedText.trim()) {
										throw new Error('Nenhum texto extraído da imagem');
									}
									console.log('Texto extraído com sucesso');
								} finally {
									await worker.terminate();
									console.log('Worker terminado');
								}
							} catch (tesseractError: any) {
								console.error('Erro detalhado do Tesseract:', tesseractError);
								throw new Error(`Erro no processamento do Tesseract: ${tesseractError.message || 'Erro desconhecido'}\nStack: ${tesseractError.stack || 'Sem stack trace'}`);
							}
							break;

						case 'ocrspace':
							const credentials = await this.getCredentials('ocrSpaceApi');
							const formData = new FormData();
							formData.append('apikey', credentials.apiKey as string);
							formData.append('language', 'por');
							formData.append('isOverlayRequired', 'true');
							formData.append('detectOrientation', 'true');
							formData.append('scale', 'true');
							formData.append('OCREngine', '2');
							
							if (fileType === 'pdf') {
								formData.append('file', new Blob([imageData], { type: 'application/pdf' }));
							} else {
								formData.append('file', new Blob([imageData]));
							}

							const response = await axios.post('https://api.ocr.space/parse/image', formData, {
								headers: { 'Content-Type': 'multipart/form-data' },
							});
							extractedText = response.data.ParsedResults[0].ParsedText || '';
							break;

						case 'textract':
							const awsCredentials = await this.getCredentials('awsTextractApi');
							const textract = new TextractClient({
								region: awsCredentials.region as string,
								credentials: {
									accessKeyId: awsCredentials.accessKeyId as string,
									secretAccessKey: awsCredentials.secretAccessKey as string,
								},
							});

							if (fileType === 'pdf') {
								const command = new AnalyzeDocumentCommand({
									Document: {
										Bytes: imageData,
									},
									FeatureTypes: ['FORMS', 'TABLES', 'QUERIES'],
								});
								const result = await textract.send(command);
								extractedText = result.Blocks?.filter(block => block.BlockType === 'LINE')
									.map(block => block.Text)
									.join('\n') || '';
							} else {
								const command = new DetectDocumentTextCommand({
									Document: {
										Bytes: imageData,
									},
								});
								const result = await textract.send(command);
								extractedText = result.Blocks?.filter(block => block.BlockType === 'LINE')
									.map(block => block.Text)
									.join('\n') || '';
							}
							break;
					}

					let finalOutput: any = extractedText;

					if (treatmentMethod === 'ocr_ai' && extractedText) {
						try {
							const credentials = await this.getCredentials('openAiApi');
							const aiEngine = this.getNodeParameter('aiEngine', i) as string;
							const aiModel = this.getNodeParameter('aiModel', i) as string;
							
							if (aiEngine === 'chatgpt') {
								const openai = new OpenAI({
									apiKey: credentials.apiKey as string,
								});

								const aiResponse = await openai.chat.completions.create({
									model: aiModel === 'gpt-4-mini' ? 'gpt-4-1106-preview' : 'gpt-4',
									messages: [
										{
											role: 'system',
											content: 'Você é um especialista em análise de documentos. Analise o texto fornecido e extraia as informações mais relevantes.',
										},
										{
											role: 'user',
											content: `Analise este texto e extraia as informações mais importantes:\n\n${extractedText}`,
										},
									],
									temperature: 0.3,
								});

								const aiAnalysis = aiResponse.choices[0]?.message?.content || 'Não foi possível analisar o texto com IA.';

								if (outputFormat === 'json') {
									finalOutput = {
										original_text: extractedText,
										ai_analysis: aiAnalysis,
									};
								} else if (outputFormat === 'csv') {
									finalOutput = `Texto Original,Análise IA\n"${extractedText.replace(/"/g, '""')}","${aiAnalysis.replace(/"/g, '""')}"`;
								} else {
									finalOutput = `=== Texto Original ===\n${extractedText}\n\n=== Análise IA ===\n${aiAnalysis}`;
								}
							}
						} catch (aiError: any) {
							throw new Error(`Erro na análise com IA: ${aiError.message}`);
						}
					}

					returnData.push({
						json: {
							success: true,
							timestamp: new Date().toISOString(),
							engine,
							fileType,
							treatmentMethod,
							outputFormat,
							data: finalOutput,
							metadata: {
								processedAt: new Date().toISOString(),
								engineVersion: {
									tesseract: '4.1.1',
									openai: '4.97.0',
								},
							},
						},
					});
				} catch (error: any) {
					console.error('Erro completo:', error);
					returnData.push({
						json: {
							success: false,
							timestamp: new Date().toISOString(),
							error: {
								message: error.message || 'Erro desconhecido',
								type: error.name || 'ProcessingError',
								stack: error.stack || 'Sem stack trace',
								context: {
									engine,
									fileType,
									treatmentMethod,
									outputFormat,
									originalError: error.toString(),
									details: typeof error === 'object' ? JSON.stringify(error) : 'Erro não é um objeto'
								},
							},
						},
					});
				}
			}

			return [returnData];
		} catch (error: any) {
			throw new Error(`Erro global na execução: ${error.message}\nDetalhes: ${error.stack}`);
		}
	}
} 