"""Fase 2: sanitiza secretos reales en las fuentes de contenido para autoria de seeds
(5 SKILL.md, 7 agentes de ~/.claude/agents/, 66 planes de ~/.claude/plans/) antes de
que ningun subagente developer los lea para escribir ejemplos de entrenamiento.

Corre localmente (no requiere GPU ni el modelo). Usa detect-secrets (entropia alta,
formatos conocidos AWS/JWT/etc.) mas una lista de regex explicitas para los patrones
ya conocidos de este proyecto (SPARK_PASSWORD, GMAIL_APP_PASSWORD, el bearer token
reusado, emails reales, IPs/hostnames internos de spark). La sustitucion es estable:
el mismo secreto real siempre produce el mismo placeholder, via un diccionario hash
persistido localmente en data/raw/.secrets_map.json (gitignoreado, solo para debug).

Salida: data/raw/sanitized/{skills,agents,plans}/... (misma estructura relativa).

Gate: termina con exit code != 0 si detect-secrets o las regex explicitas encuentran
algo en la salida YA sanitizada -- falla cerrado, no adivina reemplazos para
patrones no reconocidos.
"""
import hashlib
import json
import re
import sys
from pathlib import Path

from detect_secrets.core import scan

REPO_ROOT = Path(__file__).resolve().parent.parent
HOME = Path.home()

SOURCES = [
    (HOME / ".claude" / "skills", "*/SKILL.md", "skills"),
    (HOME / ".claude" / "agents", "*.md", "agents"),
    (HOME / ".claude" / "plans", "*.md", "plans"),
]

OUTPUT_ROOT = REPO_ROOT / "data" / "raw" / "sanitized"
SECRETS_MAP_PATH = REPO_ROOT / "data" / "raw" / ".secrets_map.json"

# Patrones explicitos conocidos de este proyecto. Cada tupla es
# (nombre_semantico, regex compilado). El grupo de captura 0 completo es lo que
# se reemplaza; si el patron tiene grupos, se reemplaza el match completo igual
# (el placeholder no intenta preservar texto circundante).
EXPLICIT_PATTERNS = [
    ("SPARK_PASSWORD", re.compile(r"\b01140102Alb\?")),
    ("BEARER_TOKEN", re.compile(r"\b7c1f76a62391a47941d7aab8369eb8f20334daf136ba88080815ff3070773a1f\b")),
    ("DB_PASSWORD", re.compile(r"\bsarh21234\b")),
    ("SPARK_IP", re.compile(r"\b10\.212\.133\.200\b")),
    ("INTERNAL_IP", re.compile(r"\b10\.212\.133\.\d{1,3}\b")),
    ("INTERNAL_SUBNET", re.compile(r"\b10\.212\.133\.0/24\b")),
    ("EMAIL", re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")),
    ("GMAIL_APP_PASSWORD_LITERAL", re.compile(r"\bGMAIL_APP_PASSWORD=[^\s\"']+")),
]

# Nombres de variables de entorno que NUNCA deben aparecer con un valor real
# asignado literalmente (uso de la variable en si, p.ej. "$SPARK_PASSWORD", esta bien).
ENV_VAR_NAMES = ["SPARK_PASSWORD", "GMAIL_APP_PASSWORD"]

PLACEHOLDER_COUNTS = {}
SECRETS_MAP = {}


def load_secrets_map():
    global SECRETS_MAP
    if SECRETS_MAP_PATH.exists():
        SECRETS_MAP = json.loads(SECRETS_MAP_PATH.read_text())


def save_secrets_map():
    SECRETS_MAP_PATH.parent.mkdir(parents=True, exist_ok=True)
    SECRETS_MAP_PATH.write_text(json.dumps(SECRETS_MAP, indent=2, ensure_ascii=False))


def stable_placeholder(semantic_name, raw_value):
    """Mismo secreto real -> mismo placeholder, vía un hash estable persistido."""
    digest = hashlib.sha256(raw_value.encode("utf-8")).hexdigest()[:8]
    if digest not in SECRETS_MAP:
        PLACEHOLDER_COUNTS[semantic_name] = PLACEHOLDER_COUNTS.get(semantic_name, 0) + 1
        placeholder = f"<<{semantic_name}_{PLACEHOLDER_COUNTS[semantic_name]}>>"
        SECRETS_MAP[digest] = {"placeholder": placeholder, "semantic_name": semantic_name}
    return SECRETS_MAP[digest]["placeholder"]


def apply_explicit_patterns(text):
    for semantic_name, pattern in EXPLICIT_PATTERNS:
        def _sub(match, semantic_name=semantic_name):
            return stable_placeholder(semantic_name, match.group(0))

        text = pattern.sub(_sub, text)
    return text


def detect_secrets_scan(text):
    """Corre los plugins default de detect-secrets sobre el texto linea por linea.
    Devuelve la lista de (line_number, secret_value) encontrados."""
    findings = []
    plugins = list(scan.get_plugins())
    for lineno, line in enumerate(text.splitlines(), start=1):
        for plugin in plugins:
            try:
                results = plugin.analyze_line(filename="<sanitize>", line=line, line_number=lineno)
            except Exception:
                continue
            if results:
                for secret in results:
                    raw = getattr(secret, "secret_value", None)
                    if raw:
                        findings.append((lineno, raw))
    return findings


def sanitize_text(text):
    text = apply_explicit_patterns(text)
    # Segunda pasada: detect-secrets sobre el resultado de las regex explicitas,
    # para capturar cualquier secreto de alta entropia no cubierto arriba.
    for lineno, raw in detect_secrets_scan(text):
        placeholder = stable_placeholder("GENERIC_SECRET", raw)
        text = text.replace(raw, placeholder)
    return text


def gate_check(text, relpath):
    """Falla cerrado: si sobrevive un patron conocido o detect-secrets encuentra
    algo en la salida YA sanitizada, es un error del gate."""
    problems = []
    for semantic_name, pattern in EXPLICIT_PATTERNS:
        if pattern.search(text):
            problems.append(f"{relpath}: patron explicito '{semantic_name}' sobrevivio la sanitizacion")
    for env_var in ENV_VAR_NAMES:
        if re.search(rf"\b{env_var}\s*=\s*[^\s$\"'][^\s]*", text):
            problems.append(f"{relpath}: posible asignacion literal de {env_var} sobrevivio")
    residual = detect_secrets_scan(text)
    if residual:
        for lineno, raw in residual:
            problems.append(f"{relpath}:{lineno}: detect-secrets encontro un secreto residual")
    return problems


def collect_files():
    files = []
    for base_dir, glob_pattern, bucket in SOURCES:
        if not base_dir.exists():
            print(f"[WARN] fuente no encontrada: {base_dir}")
            continue
        for path in sorted(base_dir.glob(glob_pattern)):
            if path.is_file():
                files.append((path, bucket))
    return files


def main():
    load_secrets_map()
    files = collect_files()
    print(f"[INFO] {len(files)} archivos fuente encontrados")

    all_problems = []
    written = 0
    for path, bucket in files:
        raw_text = path.read_text(encoding="utf-8")
        sanitized_text = sanitize_text(raw_text)

        if bucket == "skills":
            relpath = Path(bucket) / path.parent.name / path.name
        else:
            relpath = Path(bucket) / path.name

        problems = gate_check(sanitized_text, relpath)
        all_problems.extend(problems)

        out_path = OUTPUT_ROOT / relpath
        out_path.parent.mkdir(parents=True, exist_ok=True)
        out_path.write_text(sanitized_text, encoding="utf-8")
        written += 1

    save_secrets_map()

    print(f"[INFO] {written} archivos sanitizados escritos en {OUTPUT_ROOT}")
    total_subs = sum(PLACEHOLDER_COUNTS.values())
    print(f"[INFO] {total_subs} secretos unicos sustituidos: {PLACEHOLDER_COUNTS}")

    if all_problems:
        print(f"[GATE FAIL] {len(all_problems)} problemas encontrados en la salida sanitizada:")
        for problem in all_problems:
            print(f"  - {problem}")
        sys.exit(1)

    print("[GATE OK] 0 secretos sobrevivientes detectados en la salida sanitizada")
    sys.exit(0)


if __name__ == "__main__":
    main()
