
from docx import Document
from docx.shared import Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from pathlib import Path
import json
import sys

PLACEHOLDERS = {
    # New template tokens -> supported JSON keys (fallbacks included)
    "{{indirizzo_destinatario}}": ["indirizzo_destinatario","indirizzo destinatario","destinatario_indirizzo"],
    "{{data}}": ["data","data_lettera"],
    "{{BP}}": ["BP","bp","codice_bp","codice BP","codiceBp"],
    "{{intestatario_contratto}}": ["intestatario_contratto","intestatario","dati_intestatario.denominazione"],
    "{{residuo}}": ["residuo","importo_totale_dovuto","totale_dovuto"],
    "{{cm_telefono}}": ["credit_manager.telefono","cm.telefono","telefono_cm"],
    "{{cm_email}}": ["credit_manager.email","cm.email","email_cm"],
    "{{cm_whatsapp}}": ["credit_manager.whatsapp","cm.whatsapp","whatsapp_cm"],
    # Backward compatibility with previous template I generated
    "{{codice_bp}}": ["BP","bp","codice_bp","codice BP","codiceBp"],
    "{{intestatario}}": ["intestatario","intestatario_contratto","dati_intestatario.denominazione"],
    "{{TABELLA_FATTURE}}": []  # handled separately
}

def get_by_path(d, path):
    """Get nested key via dot notation, return '' if missing."""
    cur = d
    for part in path.split("."):
        if isinstance(cur, dict) and part in cur:
            cur = cur[part]
        else:
            return ""
    return cur if cur is not None else ""

def value_for_token(data, token):
    keys = PLACEHOLDERS[token]
    for k in keys:
        if "." in k:
            val = get_by_path(data, k)
        else:
            val = data.get(k, "")
        if val not in (None, ""):
            return str(val)
    return ""

def replace_in_paragraph(paragraph, mapping):
    """
    Sostituisce i placeholder in un paragrafo.
    Gestisce il caso in cui Word divida i placeholder tra più runs.
    """
    # Controlla se c'è qualcosa da sostituire nel testo completo
    full_text = paragraph.text
    needs_replacement = False
    for token in mapping.keys():
        if token in full_text:
            needs_replacement = True
            break
    
    if not needs_replacement:
        return  # Nessun placeholder trovato, skip
    
    # Se ci sono placeholder, ricostruisci il testo completo sostituendo
    new_text = full_text
    for token, value in mapping.items():
        if token in new_text:
            new_text = new_text.replace(token, value)
    
    # Se il testo è cambiato, aggiorna il paragrafo
    if new_text != full_text:
        # Cancella tutti i runs esistenti
        for run in paragraph.runs:
            run.text = ""
        # Crea un nuovo run con il testo sostituito
        if paragraph.runs:
            paragraph.runs[0].text = new_text
        else:
            paragraph.add_run(new_text)

def replace_in_table(table, mapping):
    for row in table.rows:
        for cell in row.cells:
            for p in cell.paragraphs:
                replace_in_paragraph(p, mapping)

def find_paragraph_with(doc, token):
    for p in doc.paragraphs:
        if token in p.text:
            return p
    return None

def insert_fatture_table(doc, after_paragraph, fatture):
    """
    Inserisce la tabella fatture DOPO il paragrafo specificato.
    Applica formattazione con font Arial, dimensione piccola e padding.
    Aggiunge spaziatura appropriata sopra e sotto la tabella.
    """
    # Trova il paragrafo e inserisci la tabella subito dopo
    p_element = after_paragraph._element
    
    # Crea la tabella con 1 riga (header) e 5 colonne
    table = doc.add_table(rows=1, cols=5)
    table_element = table._element
    
    # Inserisci la tabella DOPO il paragrafo nel documento XML
    p_element.addnext(table_element)
    
    # Aggiungi un paragrafo vuoto DOPO la tabella per spaziatura
    empty_p = OxmlElement('w:p')
    table_element.addnext(empty_p)
    
    # Stile tabella: bordi visibili
    table.style = 'Table Grid'
    
    # Rimuovi eventuali spaziature eccessive nel paragrafo placeholder
    # Imposta spacing before e after a 0 per il paragrafo del placeholder
    pPr = after_paragraph._element.get_or_add_pPr()
    spacing = OxmlElement('w:spacing')
    spacing.set(qn('w:before'), '0')
    spacing.set(qn('w:after'), '120')  # 6pt dopo
    pPr.append(spacing)
    
    # Funzione helper per aggiungere padding alle celle
    def set_cell_padding(cell, top=100, bottom=100, left=100, right=100):
        """Imposta padding in twips (1/20 di punto). 100 twips = 5pt"""
        tc = cell._element
        tcPr = tc.get_or_add_tcPr()
        tcMar = OxmlElement('w:tcMar')
        
        for margin_name, value in [('top', top), ('bottom', bottom), ('left', left), ('right', right)]:
            node = OxmlElement(f'w:{margin_name}')
            node.set(qn('w:w'), str(value))
            node.set(qn('w:type'), 'dxa')
            tcMar.append(node)
        
        tcPr.append(tcMar)
    
    # Headers
    headers = ["CONTO CONTRATTUALE", "FATTURA", "SCADENZA", "IMPORTO", "INDIRIZZO UTENZA"]
    header_row = table.rows[0]
    for cell, title in zip(header_row.cells, headers):
        # Imposta padding
        set_cell_padding(cell, top=80, bottom=80, left=100, right=100)
        
        # Rimuovi paragrafi esistenti e crea nuovo con formattazione
        cell.text = ''
        p = cell.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        run = p.add_run(title)
        run.font.name = 'Arial'
        run.font.size = Pt(9)
        run.font.bold = True
    
    # Righe dati
    for f in fatture:
        row = table.add_row()
        
        # Dati celle
        data_cells = [
            str(f.get("conto_contrattuale", "")),
            str(f.get("fattura", "")),
            str(f.get("scadenza", "")),
            "",  # Importo - gestito separatamente
            str(f.get("indirizzo_utenza", ""))
        ]
        
        # Formatta importo con simbolo €
        imp = f.get("importo", "")
        if isinstance(imp, (int, float)):
            imp_formatted = f"{imp:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
            data_cells[3] = f"€ {imp_formatted}"
        else:
            data_cells[3] = str(imp) if imp else ""
        
        # Applica dati e formattazione a tutte le celle
        for idx, (cell, text) in enumerate(zip(row.cells, data_cells)):
            # Imposta padding
            set_cell_padding(cell, top=60, bottom=60, left=60, right=60)
            
            # Imposta testo con formattazione
            cell.text = ''
            p = cell.paragraphs[0]
            run = p.add_run(text)
            run.font.name = 'Arial'
            run.font.size = Pt(8)
            
            # Per l'ultima colonna (indirizzo), disabilita word wrap
            if idx == 4:  # Colonna INDIRIZZO UTENZA
                tc = cell._element
                tcPr = tc.get_or_add_tcPr()
                noWrap = OxmlElement('w:noWrap')
                tcPr.append(noWrap)
    
    return table

def generate(json_path, template_path, out_path):
    print(f"[DEBUG] Caricamento JSON: {json_path}")
    data = json.loads(Path(json_path).read_text(encoding="utf-8"))
    
    print(f"[DEBUG] Dati JSON caricati:")
    print(f"  - intestatario_contratto: '{data.get('intestatario_contratto', 'MANCANTE')}'")
    print(f"  - BP: '{data.get('BP', 'MANCANTE')}'")
    print(f"  - data: '{data.get('data', 'MANCANTE')}'")
    print(f"  - residuo: '{data.get('residuo', 'MANCANTE')}'")
    
    print(f"[DEBUG] Caricamento template: {template_path}")
    doc = Document(template_path)

    # Build mapping for all tokens present in the document
    mapping = {}
    # Scan paragraphs and tables to know which tokens appear
    tokens_present = set()
    for p in doc.paragraphs:
        for token in PLACEHOLDERS.keys():
            if token in p.text:
                tokens_present.add(token)
    for t in doc.tables:
        for row in t.rows:
            for cell in row.cells:
                for p in cell.paragraphs:
                    for token in PLACEHOLDERS.keys():
                        if token in p.text:
                            tokens_present.add(token)

    print(f"[DEBUG] Token trovati nel template: {tokens_present}")

    for token in tokens_present:
        if token in ("{{TABELLA_FATTURE}}","{{tabella_fatture}}"):
            continue
        mapping[token] = value_for_token(data, token)
    
    print(f"[DEBUG] Mapping creato:")
    for token, value in mapping.items():
        status = "OK" if value else "VUOTO"
        print(f"  [{status}] {token} = '{value}'")

    # Replace tokens in paragraphs
    print(f"[DEBUG] Inizio sostituzione nei paragrafi...")
    replaced_count = 0
    for i, p in enumerate(doc.paragraphs):
        old_text = p.text
        replace_in_paragraph(p, mapping)
        if p.text != old_text:
            replaced_count += 1
            print(f"  [SOSTITUITO] Paragrafo {i}: '{old_text[:50]}...' -> '{p.text[:50]}...'")
    print(f"[DEBUG] Paragrafi modificati: {replaced_count}/{len(doc.paragraphs)}")
    
    # Replace in tables as well
    print(f"[DEBUG] Inizio sostituzione nelle tabelle...")
    table_replaced = 0
    for t in doc.tables:
        replace_in_table(t, mapping)
        table_replaced += 1
    print(f"[DEBUG] Tabelle processate: {table_replaced}")

    # Handle fatture table token (supports both {{tabella_fatture}} and {{TABELLA_FATTURE}})
    fatture = data.get("fatture", [])
    print(f"[DEBUG] Gestione tabella fatture: {len(fatture)} righe")
    for token in ("{{tabella_fatture}}","{{TABELLA_FATTURE}}"):
        p = find_paragraph_with(doc, token)
        if p is not None:
            print(f"[DEBUG] Trovato token tabella: {token}")
            p.text = p.text.replace(token, "")
            insert_fatture_table(doc, p, fatture)
            break  # insert only once

    print(f"[DEBUG] Salvataggio documento: {out_path}")
    
    # Il nome file è già corretto (passato da PHP con BP se disponibile)
    # Non serve rinominare
    
    Path(out_path).parent.mkdir(parents=True, exist_ok=True)
    doc.save(out_path)
    print(f"[DEBUG] ✓ Documento salvato con successo: {out_path}")
    
    return out_path  # Ritorna il percorso del file salvato

def convert_docx_to_pdf(docx_path, pdf_output_dir=None):
    """
    Converte un file DOCX in PDF usando LibreOffice headless.
    
    Args:
        docx_path: Percorso completo del file DOCX
        pdf_output_dir: Directory di output per il PDF (opzionale, default: stessa dir del DOCX)
    
    Returns:
        str: Percorso del file PDF generato
    """
    import subprocess
    import os
    
    docx_path = Path(docx_path)
    
    if not docx_path.exists():
        raise FileNotFoundError(f"File DOCX non trovato: {docx_path}")
    
    # Determina directory di output
    if pdf_output_dir is None:
        pdf_output_dir = docx_path.parent
    else:
        pdf_output_dir = Path(pdf_output_dir)
        pdf_output_dir.mkdir(parents=True, exist_ok=True)
    
    print(f"[DEBUG] Conversione DOCX -> PDF: {docx_path.name}")
    
    # Crea directory temporanea per LibreOffice (per evitare problemi di permessi)
    import tempfile
    temp_home = tempfile.mkdtemp(prefix='libreoffice_')
    
    # Comando LibreOffice headless
    # --headless: nessuna GUI
    # --convert-to pdf: formato output
    # --outdir: directory destinazione
    # -env:UserInstallation: directory temporanea per profilo utente
    command = [
        'libreoffice',
        '--headless',
        '--convert-to', 'pdf',
        '--outdir', str(pdf_output_dir),
        f'-env:UserInstallation=file://{temp_home}',
        str(docx_path)
    ]
    
    # Imposta HOME per evitare problemi con dconf/cache
    env = os.environ.copy()
    env['HOME'] = temp_home
    
    try:
        result = subprocess.run(
            command,
            capture_output=True,
            text=True,
            timeout=30,  # timeout 30 secondi
            env=env  # Usa environment modificato
        )
        
        if result.returncode != 0:
            print(f"[ERRORE] LibreOffice exit code: {result.returncode}")
            print(f"[ERRORE] stdout: {result.stdout}")
            print(f"[ERRORE] stderr: {result.stderr}")
            raise RuntimeError(f"Conversione PDF fallita: {result.stderr}")
        
        # Calcola percorso PDF generato
        pdf_path = pdf_output_dir / docx_path.with_suffix('.pdf').name
        
        if not pdf_path.exists():
            raise FileNotFoundError(f"PDF non generato: {pdf_path}")
        
        print(f"[DEBUG] ✓ PDF generato: {pdf_path}")
        return str(pdf_path)
        
    except subprocess.TimeoutExpired:
        raise RuntimeError("Timeout conversione PDF (>30s)")
    except FileNotFoundError:
        raise RuntimeError(
            "LibreOffice non trovato. Installare con: sudo apt-get install libreoffice-writer"
        )
    finally:
        # Pulisci directory temporanea
        import shutil
        try:
            shutil.rmtree(temp_home, ignore_errors=True)
        except:
            pass

if __name__ == "__main__":
    if len(sys.argv) != 4:
        print("Usage: python generate_letter.py <dati.json> <template.docx> <output.docx>")
        sys.exit(1)
    
    # Genera DOCX
    docx_path = generate(sys.argv[1], sys.argv[2], sys.argv[3])
    
    # Converti in PDF - calcola directory PDF dalla directory DOCX
    try:
        docx_path_obj = Path(docx_path)
        
        # Calcola percorso PDF: sostituisci /docx/solleciti/ con /pdf/solleciti/
        # Funziona sia su Windows che Linux
        docx_dir_str = str(docx_path_obj.parent)
        
        if '/docx/solleciti' in docx_dir_str or '\\docx\\solleciti' in docx_dir_str:
            # Sostituisci docx/solleciti con pdf/solleciti
            pdf_dir_str = docx_dir_str.replace('/docx/solleciti', '/pdf/solleciti')
            pdf_dir_str = pdf_dir_str.replace('\\docx\\solleciti', '\\pdf\\solleciti')
            pdf_output_dir = Path(pdf_dir_str)
        else:
            # Fallback: stessa directory del DOCX
            pdf_output_dir = docx_path_obj.parent
        
        # Crea directory PDF se non esiste
        pdf_output_dir.mkdir(parents=True, exist_ok=True)
        
        pdf_path = convert_docx_to_pdf(docx_path, str(pdf_output_dir))
        print(f"[SUCCESS] DOCX: {docx_path}")
        print(f"[SUCCESS] PDF:  {pdf_path}")
    except Exception as e:
        print(f"[WARNING] Errore conversione PDF: {e}")
        print(f"[WARNING] DOCX generato ma PDF non disponibile")
        sys.exit(0)  # Non fallire se solo il PDF ha problemi
