#!/usr/bin/env python3
"""
extract_mercantil_pdf.py

Extrae movimientos bancarios de PDFs de estado de cuenta de Banco Mercantil.
Usa las coordenadas X de los montos para determinar si son CARGOS o ABONOS.

Estructura del PDF:
- Dos columnas espejo (izquierda y derecha)
- Cada línea de movimiento: FECHA REFERENCIA DESCRIPCION MONTO
- Los montos pueden estar en columna CARGOS o ABONOS según su posición X

Rangos X calibrados:
- CARGOS izq: X <= 213 (después de la descripción)
- ABONOS izq: 213 < X <= 265
- SALDO izq:  X > 265 (ignorar)
- CARGOS der: X <= 601 (en mitad derecha)
- ABONOS der: 601 < X <= 653
- SALDO der:  X > 653 (ignorar)
"""

import sys
import json
import re

try:
    import pdfplumber
except ImportError:
    print(json.dumps({"error": "pdfplumber no está instalado"}))
    sys.exit(1)


def parse_monto_ve(s: str) -> float:
    """Convierte monto venezolano (1.234,56) a float"""
    s = str(s).replace('.', '').replace(',', '.')
    try:
        return abs(float(s))
    except:
        return 0.0


def extract_movimientos(pdf_path: str) -> dict:
    try:
        pdf = pdfplumber.open(pdf_path)
    except Exception as e:
        return {"error": f"No se pudo abrir el PDF: {str(e)}"}
    
    total_pages = len(pdf.pages)
    
    # Obtener año y saldo inicial del resumen
    year_suffix = "25"
    saldo_inicial = 0.0
    
    # Buscar año en las primeras páginas
    for i in range(min(3, len(pdf.pages))):
        page_text = pdf.pages[i].extract_text() or ""
        year_match = re.search(r'(\d{2})-(\d{2})-(\d{2})', page_text)
        if year_match:
            year_suffix = year_match.group(3)
            break
    
    # Buscar saldo inicial en las primeras páginas
    for i in range(min(3, len(pdf.pages))):
        page_text = pdf.pages[i].extract_text() or ""
        page_clean = page_text.replace(' ', '')
        
        # Patrón 1: "0111 SALDOALINICIODELPERIODO 93.783,48"
        match = re.search(r'0111\s*SALDOALINICIODELPERIODO\s*([\d.,]+)', page_clean)
        if match:
            saldo_inicial = parse_monto_ve(match.group(1))
            break
        
        # Patrón 2: "SALDOALINICIODELPERIODO: 93.783,48"
        match = re.search(r'SALDOALINICIODELPERIODO[:\s]*([\d.,]+)', page_clean, re.IGNORECASE)
        if match:
            saldo_inicial = parse_monto_ve(match.group(1))
            break
    
    all_movimientos = []
    pages_processed = 0
    
    # Constantes de posición calibradas
    PAGE_MIDPOINT = 396
    # Rangos para clasificar montos (columna izquierda)
    LEFT_CARGO_MAX_X = 213      # Montos con X <= 213 son CARGOS
    LEFT_ABONO_MAX_X = 265      # Montos con 213 < X <= 265 son ABONOS
    LEFT_SALDO_MIN_X = 265      # Montos con X > 265 son SALDO
    LEFT_SALDO_MAX_X = 320      # Rango máximo para saldo izquierdo
    # Rangos para columna derecha
    RIGHT_CARGO_MAX_X = 601     # Montos en derecha con X <= 601 son CARGOS
    RIGHT_ABONO_MAX_X = 653     # Montos con 601 < X <= 653 son ABONOS
    RIGHT_SALDO_MIN_X = 653     # Montos con X > 653 son SALDO
    RIGHT_SALDO_MAX_X = 710     # Rango máximo para saldo derecho
    
    for page_num, page in enumerate(pdf.pages):
        page_text = page.extract_text() or ''
        
        # Procesar páginas que tienen MOVIMIENTOSDECUENTA
        # Incluso si también tienen MERCANTILENLINEA (páginas de transición)
        if 'MOVIMIENTOSDECUENTA' not in page_text:
            continue
        
        # Saltar páginas que son SOLO de puntos de venta (sin movimientos normales)
        if 'PUNTOSDEVENTA' in page_text.replace(' ', '') and 'FECHA NUMERO DESCRIPCION' not in page_text:
            continue
        
        pages_processed += 1
        words = page.extract_words()
        
        # Agrupar por Y (línea)
        lines = {}
        for w in words:
            y = round(w['top'], 0)
            if y not in lines:
                lines[y] = []
            lines[y].append({
                'x': w['x0'],
                'x1': w['x1'],
                'text': w['text']
            })
        
        # Procesar cada línea
        for y_coord, words_in_line in sorted(lines.items()):
            ws = sorted(words_in_line, key=lambda w: w['x'])
            
            # Buscar fechas (patrón dd/mm)
            for i, w in enumerate(ws):
                if not re.match(r'^\d{2}/\d{2}$', w['text']):
                    continue
                
                fecha = w['text']
                x_fecha = w['x']
                is_left = x_fecha < PAGE_MIDPOINT
                
                # Buscar referencia (número de 6+ dígitos después de la fecha)
                referencia = ''
                descripcion_parts = []
                monto_info = None
                saldo_info = None
                
                for j in range(i + 1, min(i + 15, len(ws))):
                    next_w = ws[j]
                    x_pos = next_w['x']
                    text = next_w['text']
                    
                    # No cruzar a la otra columna
                    if is_left and x_pos > PAGE_MIDPOINT:
                        break
                    if not is_left and x_pos < PAGE_MIDPOINT:
                        break
                    
                    # Ignorar montos en zona "PORBS." (info complementaria, X~51-120)
                    if is_left and 50 <= x_pos <= 130:
                        if re.match(r'^[\d.,]+$', text) and ',' in text:
                            continue  # Skip, es "PORBS."
                    if not is_left and 440 <= x_pos <= 520:
                        if re.match(r'^[\d.,]+$', text) and ',' in text:
                            continue  # Skip
                    
                    # Buscar referencia
                    if not referencia and re.match(r'^\d{6,}$', text):
                        referencia = text
                        continue
                    
                    # Buscar monto (en zona CARGOS o ABONOS) y saldo
                    if re.match(r'^[\d.,]+$', text) and ',' in text:
                        val = parse_monto_ve(text)
                        if val > 0 and val < 50000000:
                            # Clasificar por posición X
                            if is_left:
                                if x_pos <= LEFT_CARGO_MAX_X:
                                    if not monto_info:
                                        monto_info = {'text': text, 'valor': val, 'tipo': 'DEBITO', 'x': x_pos}
                                elif x_pos <= LEFT_ABONO_MAX_X:
                                    if not monto_info:
                                        monto_info = {'text': text, 'valor': val, 'tipo': 'CREDITO', 'x': x_pos}
                                elif LEFT_SALDO_MIN_X < x_pos <= LEFT_SALDO_MAX_X:
                                    # Es SALDO
                                    if monto_info and not saldo_info:
                                        saldo_info = {'text': text, 'valor': val}
                            else:
                                if x_pos <= RIGHT_CARGO_MAX_X:
                                    if not monto_info:
                                        monto_info = {'text': text, 'valor': val, 'tipo': 'DEBITO', 'x': x_pos}
                                elif x_pos <= RIGHT_ABONO_MAX_X:
                                    if not monto_info:
                                        monto_info = {'text': text, 'valor': val, 'tipo': 'CREDITO', 'x': x_pos}
                                elif RIGHT_SALDO_MIN_X < x_pos <= RIGHT_SALDO_MAX_X:
                                    # Es SALDO
                                    if monto_info and not saldo_info:
                                        saldo_info = {'text': text, 'valor': val}
                    
                    # Acumular descripción
                    if referencia and not re.match(r'^\d+$', text):
                        descripcion_parts.append(text)
                
                # Guardar movimiento si tiene referencia y monto
                if referencia and monto_info:
                    descripcion = ''.join(descripcion_parts)
                    
                    # Filtrar saldo inicial
                    if 'SALDOALINICIODELPERIODO' in descripcion.upper().replace(' ', ''):
                        continue
                    
                    fecha_completa = f"{fecha}/{year_suffix}"
                    
                    all_movimientos.append({
                        'fecha': fecha_completa,
                        'referencia': referencia,
                        'descripcion': descripcion,
                        'monto': monto_info['text'],
                        'saldo': saldo_info['text'] if saldo_info else '',
                        'tipo': monto_info['tipo'],
                        'pagina': page_num + 1,
                        'columna': 'izq' if is_left else 'der'
                    })
    
    pdf.close()
    
    # Eliminar duplicados
    seen = set()
    unique = []
    for m in all_movimientos:
        key = (m['referencia'], m['fecha'], m['monto'], m['tipo'])
        if key not in seen:
            seen.add(key)
            unique.append(m)
    
    # Ordenar por fecha y página para calcular saldo acumulado
    def parse_date_key(m):
        # Formato: dd/mm/yy
        parts = m['fecha'].split('/')
        if len(parts) == 3:
            return (int('20' + parts[2]), int(parts[1]), int(parts[0]), m['pagina'], id(m))
        return (0, 0, 0, 0, 0)
    
    unique_sorted = sorted(unique, key=parse_date_key)
    
    # Calcular saldo acumulado
    saldo_actual = saldo_inicial
    for m in unique_sorted:
        monto_val = parse_monto_ve(m['monto'])
        if m['tipo'] == 'CREDITO':
            saldo_actual += monto_val
        else:  # DEBITO
            saldo_actual -= monto_val
        # Actualizar el campo saldo con el saldo después del movimiento
        m['saldo'] = f"{saldo_actual:,.2f}".replace(',', 'X').replace('.', ',').replace('X', '.')
    
    # Calcular resumen
    creditos = [m for m in unique_sorted if m['tipo'] == 'CREDITO']
    debitos = [m for m in unique_sorted if m['tipo'] == 'DEBITO']
    
    total_creditos = sum(parse_monto_ve(m['monto']) for m in creditos)
    total_debitos = sum(parse_monto_ve(m['monto']) for m in debitos)
    
    return {
        'movimientos': unique_sorted,
        'resumen': {
            'total_movimientos': len(unique_sorted),
            'cantidad_creditos': len(creditos),
            'cantidad_debitos': len(debitos),
            'suma_creditos': round(total_creditos, 2),
            'suma_debitos': round(total_debitos, 2),
            'balance': round(total_creditos - total_debitos, 2),
            'saldo_inicial': saldo_inicial,
            'saldo_final': round(saldo_actual, 2),
            'paginas_procesadas': pages_processed,
            'total_paginas': total_pages
        }
    }


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print(json.dumps({"error": "Uso: python3 extract_mercantil_pdf.py <ruta_pdf>"}))
        sys.exit(1)
    
    pdf_path = sys.argv[1]
    result = extract_movimientos(pdf_path)
    print(json.dumps(result, ensure_ascii=False))
