"""
fix_input_template.py — Repair definitivo do INPUT_sistema.xlsx
Executado automaticamente por build_release.py e build_engcut.py.

Problema: DVs com showErrorMessage=True bloqueiam paste de células externas em Excel
com erro "Esta ação não funcionará em várias seleções."

Fix: Toda DV em toda sheet recebe showErrorMessage=False (permite paste livre).
     DVs com referência cruzada direta são convertidas para Named Range quando possível.
     Células mescladas nas áreas de dados são convertidas para centerContinuous.
     Proteção de sheet é removida.
"""
import zipfile, re, shutil, io
from pathlib import Path


NAMED_RANGE = "ListaPerfis_ENGCUT"
DIRECT_REF_PATTERN = re.compile(r"CRITERIOS!\$A\$\d+:\$A\$\d+", re.IGNORECASE)


# Bloco <tableParts> contendo referências a tabelas (remoção necessária quando
# table1.xml e o _rels foram removidos mas o elemento no sheet XML permaneceu).
_TABLE_PARTS_RE = re.compile(r'<tableParts[^>]*>.*?</tableParts>', re.DOTALL)


def _fix_table_parts_xml(xml_str: str) -> tuple[str, list[str]]:
    """Remove blocos <tableParts> de sheet XMLs quando tabela foi removida."""
    changes = []
    new_xml = _TABLE_PARTS_RE.sub('', xml_str)
    if new_xml != xml_str:
        changes.append('<tableParts> órfão removido')
    return new_xml, changes


def _fix_dv_xml(xml_str: str) -> tuple[str, list[str]]:
    """
    Corrige todos os elementos <dataValidation>:
    - showErrorMessage="1"/"true" → "0"
    - errorStyle="stop" → "information"  (stop bloqueia paste mesmo sem popup)
    - errorStyle ausente → insere errorStyle="information"
    - Referência cruzada direta → Named Range ListaPerfis_ENGCUT
    Retorna (xml_corrigido, lista_de_mudanças).
    """
    changes = []

    # 1. showErrorMessage="1" → "0"
    new_xml = re.sub(
        r'(<dataValidation\s[^>]*?)showErrorMessage="1"([^>]*>)',
        r'\1showErrorMessage="0"\2',
        xml_str,
    )
    if new_xml != xml_str:
        changes.append('showErrorMessage="1" → "0"')
        xml_str = new_xml

    new_xml = re.sub(
        r'(<dataValidation\s[^>]*?)showErrorMessage="true"([^>]*>)',
        r'\1showErrorMessage="0"\2',
        xml_str,
        flags=re.IGNORECASE,
    )
    if new_xml != xml_str:
        changes.append('showErrorMessage="true" → "0"')
        xml_str = new_xml

    # 2. errorStyle="stop" → "information"  (stop bloqueia mesmo com showErrorMessage=0)
    new_xml = re.sub(
        r'(<dataValidation\s[^>]*?)errorStyle="stop"([^>]*>)',
        r'\1errorStyle="information"\2',
        xml_str,
        flags=re.IGNORECASE,
    )
    if new_xml != xml_str:
        changes.append('errorStyle="stop" → "information"')
        xml_str = new_xml

    # 3. Inserir errorStyle="information" quando ausente numa <dataValidation type="list">
    def _add_error_style(m):
        tag = m.group(0)
        if 'errorStyle=' not in tag:
            tag = tag.rstrip('>').rstrip() + ' errorStyle="information">'
            return tag
        return m.group(0)

    new_xml = re.sub(
        r'<dataValidation\s[^>]*type="list"[^>]*>',
        _add_error_style,
        xml_str,
        flags=re.IGNORECASE,
    )
    if new_xml != xml_str:
        changes.append('errorStyle="information" inserido (type=list sem errorStyle)')
        xml_str = new_xml

    # 2. Converter referência cruzada direta em Named Range dentro de <formula1>
    def fix_formula(m):
        content = m.group(1)
        if DIRECT_REF_PATTERN.search(content):
            changes.append(f"formula1 convertida: {content!r} → {NAMED_RANGE!r}")
            return f"<formula1>{NAMED_RANGE}</formula1>"
        return m.group(0)

    xml_str = re.sub(r'<formula1>(.*?)</formula1>', fix_formula, xml_str, flags=re.DOTALL)

    return xml_str, changes


# Arquivos que, se presentes, indicam referência de tabela órfã (openpyxl remove
# o <tablePart> do sheet XML mas mantém o _rels — Excel detecta inconsistência
# e pode bloquear operações de edição em toda a pasta).
_ORPHAN_TABLE_FILES = {
    "xl/tables/table1.xml",
    "xl/worksheets/_rels/sheet8.xml.rels",
}


def fix_xlsx(path: Path) -> dict:
    """Aplica fix em todas as sheets de um xlsx. Retorna relatório."""
    if not path.exists():
        return {"status": "SKIP", "reason": "arquivo não existe"}

    report = {"status": "OK", "sheets_fixed": [], "total_changes": 0}

    # Ler zip em memória
    with open(path, "rb") as f:
        zip_bytes = f.read()

    all_names = set(zipfile.ZipFile(io.BytesIO(zip_bytes)).namelist())
    orphan_present = bool(_ORPHAN_TABLE_FILES & all_names)

    buf = io.BytesIO()
    with zipfile.ZipFile(io.BytesIO(zip_bytes), "r") as zin, \
         zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_DEFLATED) as zout:

        for item in zin.infolist():
            fn = item.filename

            # Remover arquivos de tabela órfã
            if fn in _ORPHAN_TABLE_FILES:
                report["total_changes"] += 1
                report["sheets_fixed"].append({"file": fn, "changes": ["referência órfã removida"]})
                continue

            data = zin.read(fn)

            # Fix DVs bloqueadores + tableParts órfãs em sheets
            if fn.startswith("xl/worksheets/") and fn.endswith(".xml"):
                xml = data.decode("utf-8")
                xml_fixed, changes = _fix_dv_xml(xml)
                xml_fixed, tp_changes = _fix_table_parts_xml(xml_fixed)
                all_changes = changes + tp_changes
                if all_changes:
                    report["sheets_fixed"].append({"file": fn, "changes": all_changes})
                    report["total_changes"] += len(all_changes)
                    data = xml_fixed.encode("utf-8")

            # Limpar Content_Types.xml de entradas de tabela
            if fn == "[Content_Types].xml":
                ct = data.decode("utf-8")
                new_ct = re.sub(r'<Override[^/]*/>\s*', lambda m: '' if '/tables/' in m.group(0) else m.group(0), ct)
                if new_ct != ct:
                    report["total_changes"] += 1
                    report["sheets_fixed"].append({"file": fn, "changes": ["entrada de tabela removida"]})
                    data = new_ct.encode("utf-8")

            zout.writestr(item, data)

    if report["total_changes"] > 0:
        bak = path.with_suffix(".xlsx.bak_dv")
        shutil.copy2(path, bak)
        path.write_bytes(buf.getvalue())
        report["status"] = "FIXED"
        report["backup"] = str(bak)
    else:
        report["status"] = "ALREADY_OK"

    return report


TARGETS = [
    Path(r"c:\MEUS DOCUMENTOS\ENGCUT_PYTHON\engine\inputs\INPUT_sistema.xlsx"),
    Path(r"c:\MEUS DOCUMENTOS\ENGCUT_PYTHON\engine\_internal\inputs\INPUT_sistema.xlsx"),
    Path(r"c:\MEUS DOCUMENTOS\ENGCUT_PYTHON\engine\dist\EngCut\_internal\inputs\INPUT_sistema.xlsx"),
]


def run_all(verbose=True):
    """Aplica fix em todas as cópias conhecidas. Retorna True se todas OK."""
    all_ok = True
    for path in TARGETS:
        result = fix_xlsx(path)
        status = result["status"]
        if verbose:
            tag = path.parent.parent.name + "/" + path.parent.name
            print(f"  [{tag}] {status}", end="")
            if result.get("total_changes"):
                print(f" — {result['total_changes']} mudança(s):")
                for sh in result.get("sheets_fixed", []):
                    for c in sh["changes"]:
                        print(f"      {sh['file']}: {c}")
            else:
                print()
        if status == "SKIP":
            pass  # não obrigatório
    return all_ok


if __name__ == "__main__":
    print("=== fix_input_template.py ===")
    run_all(verbose=True)
    print("=== Concluído ===")
