"""
Modulo lettore — Lettura dei file Excel del capitolato e dei preventivi imprese.

Tutte le funzioni leggono i file .xlsx/.xls con openpyxl e restituiscono
strutture dati Python standard (liste di dizionari).
"""

from openpyxl import load_workbook


# ─────────────────────────────────────────────────────────────────
#  HELPER PRIVATI
# ─────────────────────────────────────────────────────────────────

def _trova_riga_hash(ws):
    """
    Cerca la riga di intestazione della tabella cercando '#' in colonna A.
    Questa riga separa l'intestazione dello studio di architettura
    dalla tabella vera e propria del computo.

    Restituisce il numero di riga (1-based) oppure None se non trovato.
    """
    for riga in ws.iter_rows(max_col=1):
        valore = riga[0].value
        if valore is not None and str(valore).strip() == "#":
            return riga[0].row
    return None


def estrai_firma_intestazione(capitolato_path):
    """
    Legge la riga con '#' in colonna A dal capitolato e restituisce
    i token delle colonne A-F come 'firma' per il riconoscimento
    della riga di intestazione nei file delle imprese.

    Usato da trova_riga_intestazione() per trovare da dove iniziano
    i dati nei preventivi che hanno righe extra in testa.

    Restituisce una lista di stringhe (token in minuscolo).
    """
    wb = load_workbook(capitolato_path, data_only=True)
    ws = wb.active
    firma = []
    riga_hash = _trova_riga_hash(ws)
    if riga_hash:
        for riga in ws.iter_rows(min_row=riga_hash, max_row=riga_hash,
                                  max_col=6, values_only=True):
            for valore in riga:
                token = str(valore).strip().lower() if valore else ""
                firma.append(token)
    wb.close()
    return firma  # es. ['#', 'descrizione delle opere', 'u.m.', ...]


def trova_riga_intestazione(ws, firma_capitolato, soglia_match=2):
    """
    Trova la riga di intestazione della tabella in un worksheet di un preventivo.

    Strategia:
    1. Prima cerca '#' in colonna A (struttura identica al capitolato)
    2. In fallback: confronto testuale con la firma del capitolato
       (almeno soglia_match token devono corrispondere)

    Match unidirezionale: il token del capitolato deve essere contenuto
    nel valore della cella (non viceversa, per evitare falsi positivi).

    Restituisce il numero di riga (1-based) oppure None.
    """
    # Tentativo 1: cerca '#' in colonna A
    for riga in ws.iter_rows(max_col=1):
        if riga[0].value is not None and str(riga[0].value).strip() == "#":
            return riga[0].row

    # Fallback: confronto testuale con la firma
    for riga in ws.iter_rows(max_col=6):
        conteggio_match = 0
        for idx, cella in enumerate(riga):
            if idx >= len(firma_capitolato):
                break
            token_firma = firma_capitolato[idx]
            if not token_firma:
                continue
            valore_cella = str(cella.value).strip().lower() if cella.value else ""
            if token_firma in valore_cella:
                conteggio_match += 1
        if conteggio_match >= soglia_match:
            return riga[0].row

    return None


# ─────────────────────────────────────────────────────────────────
#  LETTURA CAPITOLATO
# ─────────────────────────────────────────────────────────────────

def leggi_capitolato(filepath):
    """
    Legge il capitolato di riferimento e restituisce una lista piatta
    con TUTTE le righe originali del file, ognuna tipizzata.

    Regola unica per classificare una riga come "dati" (voce prezzabile):
      → col D contiene un valore numerico (la quantità)
    Tutto il resto viene copiato fedelmente come testo/vuota.

    Tipi di riga restituiti:
      {"tipo": "vuota"}
      {"tipo": "testo",  "col_a": str|None, "col_b": str|None}
      {"tipo": "dati",   "col_a": str|None, "col_b": str|None,
                         "col_c": str|None, "col_d": float, "num_voce": str}

    Il campo "num_voce" nelle righe "dati" è un identificatore univoco
    della voce (col A se presente, altrimenti progressivo interno).
    """
    wb = load_workbook(filepath, data_only=True)
    ws = wb.active

    # Trova la riga con '#' (inizio della tabella vera)
    riga_intestazione = _trova_riga_hash(ws)
    if riga_intestazione is None:
        riga_intestazione = 1  # fallback: parti dall'inizio

    righe = []
    contatore_voce = 0  # usato per generare num_voce univoco quando col A è vuota

    for riga in ws.iter_rows(min_row=riga_intestazione + 1, values_only=True):
        col_a = riga[0] if len(riga) > 0 else None
        col_b = riga[1] if len(riga) > 1 else None
        col_c = riga[2] if len(riga) > 2 else None
        col_d = riga[3] if len(riga) > 3 else None

        # ── Riga completamente vuota ──
        if all(v is None for v in (col_a, col_b, col_c, col_d)):
            righe.append({"tipo": "vuota"})
            continue

        # ── Riga dati: col D contiene un numero (quantità) ──
        try:
            qta = float(col_d)
            e_dati = True
        except (ValueError, TypeError):
            qta = None
            e_dati = False

        if e_dati:
            contatore_voce += 1
            # Se col A ha un valore usalo come chiave, altrimenti usa il progressivo
            num_voce = str(col_a).strip() if col_a is not None else f"__v{contatore_voce}"
            righe.append({
                "tipo":     "dati",
                "col_a":    str(col_a).strip() if col_a is not None else None,
                "col_b":    str(col_b).strip() if col_b is not None else None,
                "col_c":    str(col_c).strip() if col_c is not None else None,
                "col_d":    qta,
                "num_voce": num_voce,
            })
        else:
            # ── Qualsiasi altra riga: copiata come testo ──
            righe.append({
                "tipo":  "testo",
                "col_a": str(col_a).strip() if col_a is not None else None,
                "col_b": str(col_b).strip() if col_b is not None else None,
            })

    wb.close()
    return righe


# ─────────────────────────────────────────────────────────────────
#  LETTURA PREVENTIVO IMPRESA
# ─────────────────────────────────────────────────────────────────

def leggi_preventivo(filepath, firma_capitolato, numeri_voce_capitolato):
    """
    Legge un preventivo Excel compilato da un'impresa.

    Regola unica: una riga è "dati" (ha il prezzo) se col D contiene un numero.
    Il prezzo unitario si trova in col E di quella stessa riga.
    Le chiavi del dizionario restituito coincidono con i num_voce del capitolato
    (col A del file impresa, se presente).

    Args:
        filepath: percorso del file Excel dell'impresa
        firma_capitolato: lista token da estrai_firma_intestazione()
        numeri_voce_capitolato: set di num_voce validi del capitolato

    Returns:
        dict{num_voce: prezzo_float_o_None}
    """
    wb = load_workbook(filepath, data_only=True)
    ws = wb.active

    # Trova la riga di intestazione (con '#' o via firma)
    riga_intestazione = trova_riga_intestazione(ws, firma_capitolato)
    if riga_intestazione is None:
        riga_intestazione = 1  # fallback

    voci = {}
    contatore = 0  # progressivo per voci senza col A

    for riga in ws.iter_rows(min_row=riga_intestazione + 1, values_only=True):
        col_a = riga[0] if len(riga) > 0 else None
        col_d = riga[3] if len(riga) > 3 else None
        col_e = riga[4] if len(riga) > 4 else None

        # Riga dati: col D è un numero
        try:
            float(col_d)
            e_dati = True
        except (ValueError, TypeError):
            e_dati = False

        if not e_dati:
            continue

        # Chiave: col A se presente, altrimenti progressivo (come nel capitolato)
        contatore += 1
        num_voce = str(col_a).strip() if col_a is not None else f"__v{contatore}"

        if num_voce in numeri_voce_capitolato:
            try:
                prezzo = float(col_e) if col_e is not None else None
            except (ValueError, TypeError):
                prezzo = None
            voci[num_voce] = prezzo

    wb.close()
    return voci
