#!/usr/bin/env python3
"""Comprobaciones estáticas del parche INTERLINK WhatsApp v2.6."""
from __future__ import annotations

import json
import os
from pathlib import Path
import re
import subprocess
import sys

ROOT = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path(__file__).resolve().parents[1]
errors: list[str] = []
checks = 0


def ok(condition: bool, message: str) -> None:
    global checks
    checks += 1
    if not condition:
        errors.append(message)
        print(f"[FAIL] {message}")
    else:
        print(f"[OK]   {message}")


required = [
    "app/AdminSupport.php",
    "app/OperationalRepositories.php",
    "app/ConversationEngine.php",
    "admin/incidents.php",
    "admin/service_technology.php",
    "admin/knowledge.php",
    "admin/ai_feedback.php",
    "sql/wa2_v26_incidents_tickets_knowledge.sql",
    "sql/wa2_v26_verify.sql",
    "sql/wa2_v26_safe_rollback.sql",
    "config/intent_policy.json",
    "prompts/system_prompt.md",
]
for rel in required:
    ok((ROOT / rel).is_file(), f"Existe {rel}")

# Sintaxis PHP del árbol de trabajo, excluyendo copias legacy no distribuibles.
php_files = sorted(
    p for p in ROOT.rglob("*.php")
    if p.name != "knowledge_legacy_v25.php"
    and not any(part in {"logs", "receipts", "media", "uploads"} for part in p.relative_to(ROOT).parts)
)
php_binary = os.environ.get("PHP_BIN", "php")
for path in php_files:
    proc = subprocess.run([php_binary, "-l", str(path)], text=True, capture_output=True)
    ok(proc.returncode == 0, f"PHP válido: {path.relative_to(ROOT)}")
    if proc.returncode != 0:
        print(proc.stdout + proc.stderr)

# JSON válido.
policy = ROOT / "config/intent_policy.json"
try:
    json.loads(policy.read_text(encoding="utf-8"))
    ok(True, "intent_policy.json válido")
except Exception as exc:  # noqa: BLE001
    ok(False, f"intent_policy.json inválido: {exc}")

# Migración principal no destructiva, explícita e idempotente respecto de ajustes manuales.
migration = (ROOT / "sql/wa2_v26_incidents_tickets_knowledge.sql").read_text(encoding="utf-8")
upper = migration.upper()
ok("USE `INTERLINK_CRM`;" in upper, "Migración selecciona interlink_crm")
ok("DROP TABLE" not in upper and "DROP DATABASE" not in upper, "Migración no elimina tablas ni bases")
ok(not re.search(r"\bDELETE\s+FROM\s+`?(CUSTOMERS|SERVICES|TICKETS|WA2_MESSAGES|WA2_CONVERSATIONS)`?", upper), "Migración no borra datos operativos")
ok("@WA2_V26_FIRST_RUN" in upper, "Migración no reclasifica conocimiento en reejecuciones posteriores")
ok("ACCESS_TECHNOLOGY" in upper, "Migración agrega tecnología de acceso")
ok("WA2_NETWORK_INCIDENTS" in upper, "Migración crea panel de incidencias")
ok("WA2_TICKET_LINKS" in upper, "Migración vincula tickets reales")
ok("WA2_KNOWLEDGE_VERSIONS" in upper and "WA2_AI_FEEDBACK" in upper, "Migración crea historial y feedback")

# El conjunto distribuible jamás incluye rutas o archivos sensibles.
forbidden_paths = [
    "config/db.local.php",
    "admin/knowledge_legacy_v25.php",
]
for rel in forbidden_paths:
    ok(rel not in required, f"Lista distribuible no incorpora {rel}")

# Solo se revisan los archivos que forman parte funcional del parche. No se escanean logs ni credenciales locales.
selected_files: list[Path] = []
for dirname in ["app", "admin", "config", "prompts", "sql", "tests", "docs"]:
    base = ROOT / dirname
    if not base.exists():
        continue
    for path in base.rglob("*"):
        if not path.is_file():
            continue
        rel = path.relative_to(ROOT)
        if rel.as_posix() in forbidden_paths:
            continue
        if any(part in {"logs", "receipts", "media", "uploads", "__pycache__"} for part in rel.parts):
            continue
        if path.suffix.lower() in {".png", ".jpg", ".jpeg", ".gif", ".zip"}:
            continue
        selected_files.append(path)

secret_patterns = [
    re.compile(r"\bsk-(?:proj-)?[A-Za-z0-9_-]{16,}"),
    re.compile(r"\bEAA[A-Za-z0-9]{40,}"),
    re.compile(r"Authorization:\s*Api-Key\s+[A-Za-z0-9._-]{12,}", re.I),
]
for path in selected_files:
    text = path.read_text(encoding="utf-8", errors="ignore")
    leaked = any(pattern.search(text) for pattern in secret_patterns)
    ok(not leaked, f"Sin secreto literal: {path.relative_to(ROOT)}")

# Referencias obligatorias de seguridad conversacional.
engine = (ROOT / "app/ConversationEngine.php").read_text(encoding="utf-8")
ops = (ROOT / "app/OperationalRepositories.php").read_text(encoding="utf-8")
repo = (ROOT / "app/Repositories.php").read_text(encoding="utf-8")
ok("createOrGet" in engine and "ticket_created" in engine, "El motor crea/reutiliza tickets antes de informarlos")
ok("activeForContext" in engine and "incident_registry" in engine, "El motor consulta incidencias verificadas")
ok("incident_not_grounded" in ops and "ticket_not_grounded" in ops, "El guard bloquea afirmaciones sin respaldo")
ok("location_already_known" in ops and "repeated_restart" in ops, "El guard evita datos y pasos repetidos")
ok("intent_key IN" in repo and "approval_status='approved'" in repo, "El conocimiento se filtra por intención y aprobación")

print(f"\nComprobaciones: {checks}; fallos: {len(errors)}")
if errors:
    sys.exit(1)
