"""Tests del script tools/vendor_enricher_python.sh (issue 0033b).

Verifica:
  - manifest sin uses_functions Python -> no crea _vendored/.
  - manifest con un uses_functions -> copia el .py + __init__.
  - dep transitiva (extract_iocs importa siblings) -> copia siblings.
  - .vendor.lock con SHA256 + path origen.
  - Idempotencia: 2da llamada con mismo estado no rehace nada.
  - Cambio en el manifest invalida el lock.
"""
from __future__ import annotations

import hashlib
import os
import shutil
import subprocess
from pathlib import Path

import pytest

from conftest import APP_DIR_SRC, REGISTRY_ROOT


SCRIPT = APP_DIR_SRC / "tools" / "vendor_enricher_python.sh"

# El script vendor es bash-only y vive en el repo dev. En la carpeta
# portable de Windows no esta presente; ademas necesitaria un bash
# real para ejecutarse. Saltamos toda la suite si:
#   - no encontramos `bash` en PATH (Windows), o
#   - el script no existe (deploy portable sin tools/).
pytestmark = pytest.mark.skipif(
    shutil.which("bash") is None or not SCRIPT.exists(),
    reason="bash o tools/vendor_enricher_python.sh no disponible "
           "(esperado en deploy portable)",
)


def _make_enricher_dir(tmp_path: Path, manifest: str) -> Path:
    enr = tmp_path / "test_enricher"
    enr.mkdir()
    (enr / "manifest.yaml").write_text(manifest, encoding="utf-8")
    (enr / "run.py").write_text("# stub\n", encoding="utf-8")
    return enr


def _run_vendor(enr_dir: Path) -> subprocess.CompletedProcess:
    return subprocess.run(
        ["bash", str(SCRIPT), str(enr_dir), str(REGISTRY_ROOT)],
        capture_output=True, text=True, timeout=20,
    )


def test_no_uses_functions_does_not_create_vendored(tmp_path):
    enr = _make_enricher_dir(tmp_path,
        "id: x\nname: x\napplies_to: [text]\n")
    proc = _run_vendor(enr)
    assert proc.returncode == 0, proc.stderr
    assert not (enr / "_vendored").exists()
    assert not (enr / ".vendor.lock").exists()


def test_single_dep_creates_vendored_layout(tmp_path):
    enr = _make_enricher_dir(tmp_path,
        "id: x\nname: x\napplies_to: [Url]\n"
        "uses_functions:\n"
        "  - normalize_url_py_cybersecurity\n")
    proc = _run_vendor(enr)
    assert proc.returncode == 0, proc.stderr
    assert (enr / "_vendored" / "__init__.py").exists()
    assert (enr / "_vendored" / "cybersecurity" / "__init__.py").exists()
    assert (enr / "_vendored" / "cybersecurity" / "cybersecurity.py").exists()
    assert (enr / ".vendor.lock").exists()
    lock = (enr / ".vendor.lock").read_text()
    assert "normalize_url_py_cybersecurity" in lock


def test_transitive_siblings_are_copied(tmp_path):
    """extract_iocs.py importa 7 modulos siblings — todos deben venir."""
    enr = _make_enricher_dir(tmp_path,
        "id: x\nname: x\napplies_to: [Webpage]\n"
        "uses_functions:\n"
        "  - extract_iocs_py_cybersecurity\n")
    proc = _run_vendor(enr)
    assert proc.returncode == 0, proc.stderr
    cyb = enr / "_vendored" / "cybersecurity"
    assert (cyb / "extract_iocs.py").exists()
    expected_siblings = {
        "extract_ip_addresses.py", "extract_emails.py",
        "extract_domains.py", "extract_file_hashes.py",
        "extract_crypto_wallets.py", "extract_cve_ids.py",
        "extract_mac_addresses.py", "extract_phone_numbers.py",
    }
    found = {p.name for p in cyb.glob("*.py")}
    missing = expected_siblings - found
    assert not missing, f"siblings no copiados: {missing}"


def test_lock_contains_correct_sha256(tmp_path):
    enr = _make_enricher_dir(tmp_path,
        "id: x\nname: x\napplies_to: [Url]\n"
        "uses_functions:\n  - normalize_url_py_cybersecurity\n")
    proc = _run_vendor(enr)
    assert proc.returncode == 0, proc.stderr

    src = REGISTRY_ROOT / "python" / "functions" / "cybersecurity" / "cybersecurity.py"
    expected_sha = hashlib.sha256(src.read_bytes()).hexdigest()

    lock = (enr / ".vendor.lock").read_text()
    assert expected_sha in lock, lock


def test_idempotency_skips_when_unchanged(tmp_path):
    enr = _make_enricher_dir(tmp_path,
        "id: x\nname: x\napplies_to: [Url]\n"
        "uses_functions:\n  - normalize_url_py_cybersecurity\n")
    p1 = _run_vendor(enr)
    assert p1.returncode == 0
    p2 = _run_vendor(enr)
    assert p2.returncode == 0
    assert "sin cambios" in p2.stdout, p2.stdout


def test_vendored_module_can_be_imported_in_isolation(tmp_path):
    """Smoke: el _vendored/ resultante es importable sin registry_root."""
    enr = _make_enricher_dir(tmp_path,
        "id: x\nname: x\napplies_to: [Webpage]\n"
        "uses_functions:\n  - extract_urls_py_cybersecurity\n")
    proc = _run_vendor(enr)
    assert proc.returncode == 0, proc.stderr

    # Lanzamos un Python externo con _vendored como unico path adicional.
    code = (
        "import sys; sys.path.insert(0, 'enrichers_test/_vendored');"
        "from cybersecurity.cybersecurity import extract_urls;"
        "print(len(extract_urls('foo http://x.com bar')))"
    )
    # Crear symlink temporal con el nombre esperado.
    fake = tmp_path / "enrichers_test"
    fake.symlink_to(enr)
    proc2 = subprocess.run(
        ["python3", "-c", code],
        cwd=str(tmp_path), capture_output=True, text=True, timeout=10,
    )
    assert proc2.returncode == 0, proc2.stderr
    assert proc2.stdout.strip() == "1"
