#!/usr/bin/env python3
"""
audit_atomicat.py
------------------
Ferramenta de auditoria/limpeza para zips exportados do Atomicat, antes de
subir a pagina pro cPanel.

O QUE ESSE SCRIPT FAZ (por padrao):
  1. Corrige links de Termos de Uso / Politica de Privacidade no rodape que
     ainda apontam pra um dominio antigo/clonado (ex: go-xarion.site) —
     troca pelo dominio real da pagina.
  2. Baixa todas as imagens que ainda estao no CDN da Atomicat
     (media.atomicatmedia.net) e reescreve os caminhos pra uma pasta local
     (assets/ por padrao), assim a pagina fica independente do CDN.
  3. Neutraliza o beacon de telemetria de erro do player (VTurb) que manda
     dados do visitante (referrer, user-agent, idioma, nome interno da
     pagina) pra apido.atomicat-api.com. Isso e so remocao de uma chamada de
     rede que vaza fbclid/parametros de clique pra terceiro sem necessidade
     — nao afeta o funcionamento da pagina em si.
  4. Injeta a funcao ReadCookie() quando ela e usada (normalmente pelo pixel
     de postback da BuyGoods) mas nao esta definida no HTML exportado —
     e por isso que o pixel de conversao nao dispara.
  5. (Opcional, --legal-zip) Mescla um zip com as paginas reais de
     Terms of Use / Privacy Policy na raiz do export, nas pastas
     terms_of_use/ e policy_privacy/ — assim o link do rodape (corrigido
     no passo 1) aponta pra uma pagina que existe de fato, e nao só pro
     dominio certo.
  6. (Opcional, --company-page) Le o bloco de dados da empresa que ja
     existe no rodape (razao social "... LTDA", endereco, telefone,
     e-mail), gera uma pagina simples com esses dados em
     <company-page-dir>/index.html (padrao: empresa/) e insere um link
     pra ela no rodape de cada HTML onde o bloco foi encontrado. Qualquer
     campo que nao for encontrado automaticamente pode ser passado na mao
     com --company-name / --company-address / --company-phone /
     --company-email.

O QUE ESSE SCRIPT **NAO** FAZ, DE PROPOSITO:
  - Nao toca no script de validacao de licenca da Atomicat (o bloco
    ofuscado que confere se sua assinatura esta ativa). Remover isso seria
    burlar o controle de licenciamento da ferramenta que voce paga pra
    usar, e isso eu nao vou automatizar. Se seu objetivo e nao depender
    mais da assinatura da Atomicat, o caminho correto e reconstruir a
    pagina fora do builder (ou falar com o suporte da Atomicat sobre um
    plano de export permanente), nao remover a checagem.

USO:
    python3 audit_atomicat.py entrada.zip saida.zip --domain seudominio.com

    Opcoes:
      --domain          dominio real da pagina (obrigatorio p/ corrigir rodape)
      --old-domain      dominio antigo a substituir (default: go-xarion.site)
      --assets-dir      nome da pasta de assets dentro do zip (default: assets)
      --cdn-host        host do CDN da Atomicat (default: media.atomicatmedia.net)
      --keep-telemetry  nao remove o beacon de telemetria (default: remove)
      --legal-zip       zip com as pastas terms_of_use/ e policy_privacy/
                         reais, pra mesclar na raiz do export
      --company-page       gera a pagina de dados da empresa a partir do rodape
      --company-page-dir   pasta da pagina de dados da empresa (default: empresa)
      --company-link-text  texto do link inserido no rodape (default: Dados da Empresa)
      --company-name       forca a razao social (se nao for detectada certo)
      --company-address    forca o endereco (se nao for detectado certo)
      --company-phone      forca o telefone (se nao for detectado certo)
      --company-email      forca o e-mail (se nao for detectado certo)
      --report-only     so gera o relatorio, nao escreve o zip de saida
"""

import argparse
import hashlib
import os
import re
import shutil
import sys
import tempfile
import urllib.request
import zipfile
from pathlib import Path

HTML_LIKE_EXT = {".html", ".htm"}
TEXT_SCAN_EXT = {".html", ".htm", ".css", ".js"}
IMAGE_EXT_PATTERN = r"[\w\-./%]+?\.(?:png|jpe?g|webp|gif|svg|ico|avif)"

COMPANY_NAME_PATTERN = re.compile(
    r"([A-ZÀ-Ý][\w À-ÿ0-9.,&'-]{2,90}?\s+LTDA\.?)", re.IGNORECASE
)
EMAIL_PATTERN = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
# Formato solto (BR: (11) 4002-8922 / 98765-4321, US: (305) 555-1234) —
# usado so como fallback quando nao acha um telefone rotulado (Tel/Fone/Phone).
PHONE_PATTERN = re.compile(r"\(?\+?\d{1,3}\)?[-.\s]?\d{3,5}[-.\s]?\d{4}")
PHONE_LABELED_PATTERN = re.compile(
    r"(?:Tel\.?|Telefone|Fone|Phone)\s*:?\s*(" + PHONE_PATTERN.pattern + r")",
    re.IGNORECASE,
)
ADDRESS_HINT_PATTERN = re.compile(
    r"([^\n<>]{0,20}(?:Rua|Av\.|Avenida|Alameda|St\.|Street|Ave\.|Avenue|"
    r"Blvd|Suite|Ste\.|Rd\.|Road)[^\n<>]{5,160})",
    re.IGNORECASE,
)
FOOTER_TAG_PATTERN = re.compile(r"<footer\b[^>]*>.*?</footer>", re.IGNORECASE | re.DOTALL)
FOOTER_DIV_OPEN_PATTERN = re.compile(
    r'<div\b[^>]*(?:class|id)="[^"]*(?:footer|rodape)[^"]*"[^>]*>',
    re.IGNORECASE,
)
TAG_PATTERN = re.compile(r"<[^>]+>")

COMPANY_PAGE_TEMPLATE = """<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dados da Empresa</title>
<style>
  body {{ font-family: -apple-system, Arial, sans-serif; max-width: 560px; margin: 60px auto; padding: 0 20px; color: #1a1a1a; }}
  h1 {{ font-size: 22px; }}
  dl {{ margin-top: 24px; }}
  dt {{ font-weight: 600; margin-top: 14px; font-size: 13px; color: #555; text-transform: uppercase; letter-spacing: .04em; }}
  dd {{ margin: 4px 0 0; font-size: 16px; }}
</style>
</head>
<body>
  <h1>Dados da Empresa</h1>
  <dl>
    <dt>Razao Social</dt><dd>{name}</dd>
    <dt>Endereco</dt><dd>{address}</dd>
    <dt>Telefone</dt><dd>{phone}</dd>
    <dt>E-mail</dt><dd>{email}</dd>
  </dl>
</body>
</html>
"""

READ_COOKIE_JS = """
function ReadCookie(name) {
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for (var i = 0; i < ca.length; i++) {
    var c = ca[i];
    while (c.charAt(0) === ' ') c = c.substring(1, c.length);
    if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
  }
  return null;
}
""".strip()


class Report:
    def __init__(self):
        self.legal_links_fixed = 0
        self.images_downloaded = 0
        self.images_failed = []
        self.telemetry_removed = 0
        self.readcookie_injected = False
        self.license_script_left_alone = False
        self.legal_pages_merged = []
        self.company_page_created = False
        self.company_fields_found = {}
        self.company_fields_missing = []
        self.company_source_file = None
        self.company_link_inserted = 0

    def summary(self):
        lines = ["=== Relatorio da auditoria ==="]
        lines.append(f"Links de Termos/Privacidade corrigidos: {self.legal_links_fixed}")
        lines.append(f"Imagens baixadas e localizadas: {self.images_downloaded}")
        if self.images_failed:
            lines.append(f"Imagens que FALHARAM ao baixar ({len(self.images_failed)}):")
            for url, err in self.images_failed:
                lines.append(f"   - {url} -> {err}")
        lines.append(f"Chamadas de telemetria removidas: {self.telemetry_removed}")
        lines.append(f"ReadCookie() injetado: {'sim' if self.readcookie_injected else 'nao foi preciso'}")
        if self.legal_pages_merged:
            lines.append(f"Paginas legais mescladas na raiz: {', '.join(self.legal_pages_merged)}")
        if self.license_script_left_alone:
            lines.append("Script de validacao de licenca da Atomicat: ENCONTRADO, mas NAO alterado (proposital).")
        if self.company_page_created:
            found = ", ".join(f"{k}={v}" for k, v in self.company_fields_found.items())
            lines.append(f"Pagina de dados da empresa criada. Campos usados: {found}")
            if self.company_source_file:
                lines.append(f"   (bloco de origem: {self.company_source_file})")
            if self.company_fields_missing:
                lines.append(
                    "   Campos NAO encontrados automaticamente (confira/preencha na mao): "
                    + ", ".join(self.company_fields_missing)
                )
            lines.append(f"   Link inserido no rodape de {self.company_link_inserted} arquivo(s).")
        return "\n".join(lines)


def extract_zip(zip_path: Path, dest_dir: Path):
    with zipfile.ZipFile(zip_path) as zf:
        zf.extractall(dest_dir)


def rezip(src_dir: Path, out_zip: Path):
    if out_zip.exists():
        out_zip.unlink()
    with zipfile.ZipFile(out_zip, "w", zipfile.ZIP_DEFLATED) as zf:
        for root, _, files in os.walk(src_dir):
            for f in files:
                full = Path(root) / f
                rel = full.relative_to(src_dir)
                zf.write(full, rel)


def iter_text_files(root: Path):
    for path in root.rglob("*"):
        if path.is_file() and path.suffix.lower() in TEXT_SCAN_EXT:
            yield path


def fix_legal_links(root: Path, old_domain: str, new_domain: str, report: Report):
    pattern = re.compile(
        r"https?://(?:www\.)?" + re.escape(old_domain) + r"(/[^\s\"'<>)]*)"
    )
    for path in iter_text_files(root):
        text = path.read_text(encoding="utf-8", errors="ignore")

        def _sub(m):
            report.legal_links_fixed += 1
            return f"https://{new_domain}{m.group(1)}"

        new_text, n = pattern.subn(_sub, text)
        if n:
            path.write_text(new_text, encoding="utf-8")


def _hash_name(url: str) -> str:
    ext = Path(url.split("?")[0]).suffix or ".bin"
    h = hashlib.sha1(url.encode()).hexdigest()[:10]
    base = Path(url.split("?")[0]).stem
    safe_base = re.sub(r"[^\w\-]", "_", base)[:40] or "img"
    return f"{safe_base}_{h}{ext}"


def download_images(root: Path, cdn_host: str, assets_dir_name: str, report: Report,
                     downloader=None):
    """downloader(url) -> bytes ; injectable for testing without real network."""
    downloader = downloader or _default_downloader
    assets_dir = root / assets_dir_name
    url_pattern = re.compile(
        r"https?://" + re.escape(cdn_host) + r"/" + IMAGE_EXT_PATTERN, re.IGNORECASE
    )
    url_to_local = {}

    # Pass 1: find every unique URL across all text files.
    all_files = list(iter_text_files(root))
    urls = set()
    for path in all_files:
        text = path.read_text(encoding="utf-8", errors="ignore")
        for m in url_pattern.finditer(text):
            urls.add(m.group(0))

    if not urls:
        return

    assets_dir.mkdir(parents=True, exist_ok=True)

    for url in sorted(urls):
        local_name = _hash_name(url)
        local_path = assets_dir / local_name
        try:
            data = downloader(url)
            local_path.write_bytes(data)
            url_to_local[url] = f"{assets_dir_name}/{local_name}"
            report.images_downloaded += 1
        except Exception as e:  # noqa: BLE001
            report.images_failed.append((url, str(e)))

    if not url_to_local:
        return

    # Pass 2: rewrite references.
    for path in all_files:
        text = path.read_text(encoding="utf-8", errors="ignore")
        original = text
        for url, local in url_to_local.items():
            text = text.replace(url, local)
        if text != original:
            path.write_text(text, encoding="utf-8")


def _default_downloader(url: str) -> bytes:
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=20) as resp:
        return resp.read()


def remove_telemetry(root: Path, telemetry_host: str, report: Report):
    """Best-effort: comenta qualquer linha de JS que referencie o host de
    telemetria. E deliberadamente conservador (linha a linha) porque nao
    temos uma amostra real do bloco ofuscado; revise o diff antes de subir
    se a pagina tiver comportamento inesperado no player de video."""
    pattern = re.compile(re.escape(telemetry_host))
    for path in root.rglob("*.js"):
        text = path.read_text(encoding="utf-8", errors="ignore")
        if not pattern.search(text):
            continue
        new_lines = []
        changed = False
        for line in text.splitlines(keepends=True):
            if pattern.search(line):
                stripped = line.strip()
                new_lines.append(f"// [auditoria] linha removida (telemetria p/ {telemetry_host}): {stripped[:120]}\n")
                changed = True
                report.telemetry_removed += 1
            else:
                new_lines.append(line)
        if changed:
            path.write_text("".join(new_lines), encoding="utf-8")

    # Also handle inline <script> blocks inside HTML files.
    for path in root.rglob("*.html"):
        text = path.read_text(encoding="utf-8", errors="ignore")
        if not pattern.search(text):
            continue

        def _clean_script(m):
            block = m.group(0)
            if not pattern.search(block):
                return block
            lines = block.splitlines(keepends=True)
            out = []
            for line in lines:
                if pattern.search(line):
                    out.append(f"// [auditoria] linha removida (telemetria p/ {telemetry_host})\n")
                    report.telemetry_removed += 1
                else:
                    out.append(line)
            return "".join(out)

        new_text = re.sub(r"<script\b[^>]*>.*?</script>", _clean_script, text, flags=re.DOTALL | re.IGNORECASE)
        if new_text != text:
            path.write_text(new_text, encoding="utf-8")


def ensure_readcookie(root: Path, report: Report):
    for path in root.rglob("*.html"):
        text = path.read_text(encoding="utf-8", errors="ignore")
        uses_it = re.search(r"\bReadCookie\s*\(", text)
        defines_it = re.search(r"function\s+ReadCookie\s*\(", text)
        if uses_it and not defines_it:
            injection = f"<script>\n{READ_COOKIE_JS}\n</script>\n"
            if "<head>" in text:
                text = text.replace("<head>", "<head>\n" + injection, 1)
            else:
                text = injection + text
            path.write_text(text, encoding="utf-8")
            report.readcookie_injected = True


def merge_legal_pages(root: Path, legal_zip: Path, report: Report):
    """Extrai o zip de paginas legais e copia as pastas (ex: terms_of_use/,
    policy_privacy/) pra raiz do export, sobrescrevendo se ja existir algo
    com o mesmo nome."""
    with tempfile.TemporaryDirectory() as tmp:
        tmp_path = Path(tmp)
        extract_zip(legal_zip, tmp_path)
        for item in tmp_path.iterdir():
            if item.name.startswith("__MACOSX") or item.name == ".DS_Store":
                continue
            dest = root / item.name
            if item.is_dir():
                shutil.copytree(item, dest, dirs_exist_ok=True)
            else:
                dest.parent.mkdir(parents=True, exist_ok=True)
                shutil.copy2(item, dest)
            report.legal_pages_merged.append(item.name)


def _strip_tags(html: str) -> str:
    return TAG_PATTERN.sub(" ", html)


def _find_footer_blocks(text: str):
    """Retorna uma lista de (inicio, fim, texto_do_bloco) pra cada bloco de
    rodape encontrado no HTML: primeiro tenta a tag <footer>...</footer>;
    se nao achar, tenta um <div class="...footer..."> e fecha o bloco
    contando profundidade de <div>/</div> (parser simples, nao um parser
    HTML de verdade — cobre a maioria dos exports da Atomicat)."""
    blocks = []
    for m in FOOTER_TAG_PATTERN.finditer(text):
        blocks.append((m.start(), m.end(), m.group(0)))
    if blocks:
        return blocks

    m = FOOTER_DIV_OPEN_PATTERN.search(text)
    if not m:
        return []
    depth = 1
    pos = m.end()
    tag_pattern = re.compile(r"<div\b[^>]*>|</div\s*>", re.IGNORECASE)
    for tm in tag_pattern.finditer(text, pos):
        if tm.group(0).lower().startswith("</div"):
            depth -= 1
        else:
            depth += 1
        if depth == 0:
            return [(m.start(), tm.end(), text[m.start():tm.end()])]
    return []


def extract_company_data(root: Path):
    """Vasculha os HTMLs do export atras de um bloco de rodape/empresa e
    tenta puxar razao social, endereco, telefone e e-mail dele. Retorna
    (path_do_arquivo_usado_ou_None, dict_com_os_campos_achados)."""
    for path in sorted(root.rglob("*.html")):
        text = path.read_text(encoding="utf-8", errors="ignore")
        for _, _, block_html in _find_footer_blocks(text):
            plain = _strip_tags(block_html)
            data = {}
            m = COMPANY_NAME_PATTERN.search(plain)
            if m:
                data["name"] = re.sub(r"\s+", " ", m.group(1)).strip(" ,.-")
            m = EMAIL_PATTERN.search(plain)
            if m:
                data["email"] = m.group(0).strip()
            m = PHONE_LABELED_PATTERN.search(plain) or PHONE_PATTERN.search(plain)
            if m:
                data["phone"] = (m.group(1) if m.re is PHONE_LABELED_PATTERN else m.group(0)).strip()
            m = ADDRESS_HINT_PATTERN.search(plain)
            if m:
                data["address"] = re.sub(r"\s+", " ", m.group(1)).strip(" ,.-")
            if data:
                return path, data
    return None, {}


def build_company_page(root: Path, data: dict, report: Report, page_dir: str,
                        overrides: dict = None):
    """Gera <page_dir>/index.html com os dados da empresa. `overrides`
    (vindo de --company-name/--company-address/--company-phone/
    --company-email) tem prioridade sobre o que foi detectado no rodape."""
    merged = dict(data)
    merged.update({k: v for k, v in (overrides or {}).items() if v})

    fields = {}
    missing = []
    for key, label in (("name", "name"), ("address", "address"),
                        ("phone", "phone"), ("email", "email")):
        value = merged.get(key)
        if value:
            fields[label] = value
        else:
            missing.append(label)

    html = COMPANY_PAGE_TEMPLATE.format(
        name=fields.get("name", "(preencher)"),
        address=fields.get("address", "(preencher)"),
        phone=fields.get("phone", "(preencher)"),
        email=fields.get("email", "(preencher)"),
    )

    out_dir = root / page_dir
    out_dir.mkdir(parents=True, exist_ok=True)
    (out_dir / "index.html").write_text(html, encoding="utf-8")

    report.company_page_created = True
    report.company_fields_found = fields
    report.company_fields_missing = missing


def insert_company_link(root: Path, page_dir: str, link_text: str, report: Report):
    """Insere um link pra <page_dir>/ dentro de cada bloco de rodape
    encontrado nos HTMLs do export (logo antes do fechamento do bloco)."""
    link_html = f'<a href="/{page_dir}/">{link_text}</a>'
    for path in root.rglob("*.html"):
        text = path.read_text(encoding="utf-8", errors="ignore")
        blocks = _find_footer_blocks(text)
        if not blocks:
            continue
        # de tras pra frente, pra nao invalidar os offsets ja calculados
        new_text = text
        inserted_here = False
        for start, end, block_html in reversed(blocks):
            if link_text in block_html:
                continue  # ja tem o link (ex: rodou a auditoria 2x)
            close_len = 9 if block_html.lower().rstrip().endswith("</footer>") else 6
            insert_at = end - close_len
            new_text = new_text[:insert_at] + link_html + new_text[insert_at:]
            inserted_here = True
        if inserted_here:
            path.write_text(new_text, encoding="utf-8")
            report.company_link_inserted += 1


def check_license_script(root: Path, report: Report):
    """Apenas detecta e reporta — nunca remove/alterar de proposito."""
    suspicious = re.compile(r"(license|licenca|atomicat).{0,200}?(validate|check|valid)", re.IGNORECASE | re.DOTALL)
    for path in root.rglob("*.html"):
        text = path.read_text(encoding="utf-8", errors="ignore")
        if suspicious.search(text):
            report.license_script_left_alone = True
            return


def run(input_zip: str, output_zip: str, domain: str, old_domain: str,
        assets_dir: str, cdn_host: str, telemetry_host: str,
        keep_telemetry: bool, report_only: bool, downloader=None,
        legal_zip: str = None, company_page: bool = False,
        company_page_dir: str = "empresa", company_link_text: str = "Dados da Empresa",
        company_overrides: dict = None) -> Report:
    report = Report()
    with tempfile.TemporaryDirectory() as tmp:
        tmp_path = Path(tmp)
        extract_zip(Path(input_zip), tmp_path)

        if domain:
            fix_legal_links(tmp_path, old_domain, domain, report)

        download_images(tmp_path, cdn_host, assets_dir, report, downloader=downloader)

        if not keep_telemetry:
            remove_telemetry(tmp_path, telemetry_host, report)

        ensure_readcookie(tmp_path, report)
        check_license_script(tmp_path, report)

        if legal_zip:
            merge_legal_pages(tmp_path, Path(legal_zip), report)

        if company_page:
            report.company_source_file, found = extract_company_data(tmp_path)
            build_company_page(tmp_path, found, report, company_page_dir, company_overrides)
            insert_company_link(tmp_path, company_page_dir, company_link_text, report)
            if report.company_source_file:
                report.company_source_file = str(
                    report.company_source_file.relative_to(tmp_path)
                )

        if not report_only:
            rezip(tmp_path, Path(output_zip))

    return report


def main():
    ap = argparse.ArgumentParser(description="Audita e limpa um zip exportado do Atomicat.")
    ap.add_argument("input_zip")
    ap.add_argument("output_zip")
    ap.add_argument("--domain", help="dominio real da pagina, ex: go-innerglow.site")
    ap.add_argument("--old-domain", default="go-xarion.site")
    ap.add_argument("--assets-dir", default="assets")
    ap.add_argument("--cdn-host", default="media.atomicatmedia.net")
    ap.add_argument("--telemetry-host", default="apido.atomicat-api.com")
    ap.add_argument("--keep-telemetry", action="store_true")
    ap.add_argument("--legal-zip", help="zip com terms_of_use/ e policy_privacy/ reais pra mesclar na raiz")
    ap.add_argument("--company-page", action="store_true",
                     help="gera a pagina de dados da empresa a partir do rodape")
    ap.add_argument("--company-page-dir", default="empresa")
    ap.add_argument("--company-link-text", default="Dados da Empresa")
    ap.add_argument("--company-name", help="forca a razao social")
    ap.add_argument("--company-address", help="forca o endereco")
    ap.add_argument("--company-phone", help="forca o telefone")
    ap.add_argument("--company-email", help="forca o e-mail")
    ap.add_argument("--report-only", action="store_true")
    args = ap.parse_args()

    if not args.domain:
        print("Aviso: --domain nao foi passado, os links de Termos/Privacidade nao serao corrigidos.", file=sys.stderr)

    report = run(
        input_zip=args.input_zip,
        output_zip=args.output_zip,
        domain=args.domain,
        old_domain=args.old_domain,
        assets_dir=args.assets_dir,
        cdn_host=args.cdn_host,
        telemetry_host=args.telemetry_host,
        keep_telemetry=args.keep_telemetry,
        report_only=args.report_only,
        legal_zip=args.legal_zip,
        company_page=args.company_page,
        company_page_dir=args.company_page_dir,
        company_link_text=args.company_link_text,
        company_overrides={
            "name": args.company_name,
            "address": args.company_address,
            "phone": args.company_phone,
            "email": args.company_email,
        },
    )
    print(report.summary())
    if not args.report_only:
        print(f"\nZip pronto: {args.output_zip}")


if __name__ == "__main__":
    main()
