# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import importlib.util
import os
import pickle
import sys
import types
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]


def _stub_module(monkeypatch, name, **attrs):
    module = types.ModuleType(name)
    for key, value in attrs.items():
        setattr(module, key, value)
    monkeypatch.setitem(sys.modules, name, module)
    return module


def _load_lmdb_dataset_module(monkeypatch, lmdb_open):
    _stub_module(monkeypatch, "cv2")
    _stub_module(
        monkeypatch, "numpy", random=types.SimpleNamespace(randint=lambda upper: 0)
    )
    _stub_module(monkeypatch, "lmdb", open=lmdb_open)
    _stub_module(monkeypatch, "PIL")
    _stub_module(monkeypatch, "PIL.Image")

    paddle_module = _stub_module(monkeypatch, "paddle")
    paddle_io_module = _stub_module(
        monkeypatch, "paddle.io", Dataset=type("Dataset", (), {})
    )
    paddle_module.io = paddle_io_module

    ppocr_module = _stub_module(monkeypatch, "ppocr")
    ppocr_data_module = _stub_module(monkeypatch, "ppocr.data")
    ppocr_data_module.__path__ = []
    ppocr_module.data = ppocr_data_module
    _stub_module(
        monkeypatch,
        "ppocr.data.imaug",
        transform=lambda data, ops: data,
        create_operators=lambda *args, **kwargs: [],
    )

    spec = importlib.util.spec_from_file_location(
        "ppocr.data.lmdb_dataset", REPO_ROOT / "ppocr" / "data" / "lmdb_dataset.py"
    )
    module = importlib.util.module_from_spec(spec)
    monkeypatch.setitem(sys.modules, "ppocr.data.lmdb_dataset", module)
    spec.loader.exec_module(module)
    return module


class _Exploit:
    def __reduce__(self):
        return (os.system, ("echo SHOULD_NOT_RUN > /tmp/paddleocr_lmdb_pickle_test",))


class _Txn:
    def __init__(self, values):
        self._values = values

    def get(self, key):
        return self._values.get(key)


class _Env:
    def __init__(self, txn):
        self._txn = txn

    def begin(self, write=False):
        return self._txn


@pytest.fixture(autouse=True)
def _cleanup_marker():
    marker = Path("/tmp/paddleocr_lmdb_pickle_test")
    marker.unlink(missing_ok=True)
    yield
    marker.unlink(missing_ok=True)


def test_tablemaster_sample_info_rejects_pickle_rce_payload(monkeypatch):
    module = _load_lmdb_dataset_module(
        monkeypatch, lmdb_open=lambda *args, **kwargs: None
    )
    payload = pickle.dumps(_Exploit())
    txn = _Txn({b"1": payload})
    dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster)

    assert dataset.get_lmdb_sample_info(txn, 1) is None
    assert not Path("/tmp/paddleocr_lmdb_pickle_test").exists()


def test_tablemaster_sample_info_accepts_expected_basic_pickle_data(monkeypatch):
    module = _load_lmdb_dataset_module(
        monkeypatch, lmdb_open=lambda *args, **kwargs: None
    )
    safe_payload = pickle.dumps(("sample.png", b"img-bytes", "raw-name\ntext\n1,2,3,4"))
    txn = _Txn({b"1": safe_payload})
    dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster)

    sample = dataset.get_lmdb_sample_info(txn, 1)

    assert sample["file_name"] == "sample.png"
    assert sample["image"] == b"img-bytes"
    assert sample["structure"] == ["text"]
    assert sample["cells"] == [{"bbox": [1, 2, 3, 4], "tokens": ["1", "2"]}]


def test_tablemaster_length_metadata_rejects_pickle_rce_payload(monkeypatch):
    payload = pickle.dumps(_Exploit())
    txn = _Txn({b"__len__": payload})
    module = _load_lmdb_dataset_module(
        monkeypatch, lmdb_open=lambda *args, **kwargs: _Env(txn)
    )
    dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster)

    with pytest.raises(pickle.UnpicklingError):
        dataset.load_hierarchical_lmdb_dataset("/tmp/ignored")

    assert not Path("/tmp/paddleocr_lmdb_pickle_test").exists()


def test_tablemaster_length_metadata_accepts_expected_integer_pickle(monkeypatch):
    txn = _Txn({b"__len__": pickle.dumps(7)})
    module = _load_lmdb_dataset_module(
        monkeypatch, lmdb_open=lambda *args, **kwargs: _Env(txn)
    )
    dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster)

    lmdb_sets = dataset.load_hierarchical_lmdb_dataset("/tmp/ignored")

    assert lmdb_sets[0]["num_samples"] == 7
