"""safe_extract_zip_entries: the guarded zip reader for agent-uploaded
archives -- path traversal, entry-count, and size-cap protection, since the
existing extract_archive()/extractall() in this module has none of these and
is not safe to reuse for externally agent-uploaded (untrusted) zips."""

import os
import shutil
import tempfile
import zipfile

import pytest
from src.integrations.archive_extract import safe_extract_zip_entries


@pytest.fixture
def zip_dir():
    # pytest's own tmp_path fixture hits a Windows permission error on this
    # machine (stale AppData\Local\Temp\pytest-of-* dir) -- use a manual temp
    # dir instead, same workaround as tests/test_agent_asset_upload.py.
    path = tempfile.mkdtemp(prefix="p2hs-archive-extract-test-")
    try:
        yield path
    finally:
        shutil.rmtree(path, ignore_errors=True)


def _make_zip(path, entries: dict[str, bytes]):
    with zipfile.ZipFile(path, "w") as zf:
        for name, data in entries.items():
            zf.writestr(name, data)
    return path


def test_extracts_every_entry_as_bytes(zip_dir):
    zpath = _make_zip(
        os.path.join(zip_dir, "site.zip"),
        {"index.html": b"<h1>Hi</h1>", "styles.css": b"body { color: red; }"},
    )
    entries = safe_extract_zip_entries(
        zpath, max_entries=10, max_entry_bytes=1_000, max_total_bytes=10_000
    )
    assert dict(entries) == {
        "index.html": b"<h1>Hi</h1>",
        "styles.css": b"body { color: red; }",
    }


def test_skips_directory_entries(zip_dir):
    zpath = os.path.join(zip_dir, "site.zip")
    with zipfile.ZipFile(zpath, "w") as zf:
        zf.writestr("assets/", b"")
        zf.writestr("assets/logo.png", b"\x89PNG...")
    entries = safe_extract_zip_entries(
        zpath, max_entries=10, max_entry_bytes=1_000, max_total_bytes=10_000
    )
    assert dict(entries) == {"assets/logo.png": b"\x89PNG..."}


@pytest.mark.parametrize(
    "bad_name", ["../outside.txt", "../../etc/passwd", "/etc/passwd", "a/../../b.txt"]
)
def test_rejects_path_traversal(zip_dir, bad_name):
    zpath = os.path.join(zip_dir, "evil.zip")
    with zipfile.ZipFile(zpath, "w") as zf:
        zf.writestr(bad_name, b"payload")
    with pytest.raises(ValueError):
        safe_extract_zip_entries(
            zpath, max_entries=10, max_entry_bytes=1_000, max_total_bytes=10_000
        )


def test_rejects_too_many_entries(zip_dir):
    zpath = os.path.join(zip_dir, "many.zip")
    with zipfile.ZipFile(zpath, "w") as zf:
        for i in range(5):
            zf.writestr(f"f{i}.txt", b"x")
    with pytest.raises(ValueError):
        safe_extract_zip_entries(
            zpath, max_entries=3, max_entry_bytes=1_000, max_total_bytes=10_000
        )


def test_rejects_a_single_entry_over_the_per_entry_cap(zip_dir):
    zpath = _make_zip(os.path.join(zip_dir, "big.zip"), {"huge.bin": b"x" * 2_000})
    with pytest.raises(ValueError):
        safe_extract_zip_entries(
            zpath, max_entries=10, max_entry_bytes=1_000, max_total_bytes=10_000
        )


def test_rejects_when_total_uncompressed_size_exceeds_cap(zip_dir):
    zpath = _make_zip(
        os.path.join(zip_dir, "total.zip"), {"a.txt": b"x" * 600, "b.txt": b"y" * 600}
    )
    with pytest.raises(ValueError):
        safe_extract_zip_entries(
            zpath, max_entries=10, max_entry_bytes=1_000, max_total_bytes=1_000
        )
