from __future__ import annotations

import json
import logging
import math
import threading
from pathlib import Path
from typing import NoReturn
from unittest.mock import Mock, patch

import pytest
from pytest import LogCaptureFixture

from pytola_core.config import ConfigMixin


@pytest.fixture(autouse=True)
def patch_root_dir(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    """自动将 ROOT_DIR 替换为临时路径，避免污染真实数据."""
    monkeypatch.setattr(ConfigMixin, "ROOT_DIR", tmp_path)


@pytest.fixture
def temp_config_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    """创建临时配置目录，并隔离 ROOT_DIR 避免污染真实数据."""
    original_root_dir = ConfigMixin.ROOT_DIR
    monkeypatch.setattr(ConfigMixin, "ROOT_DIR", tmp_path)
    yield tmp_path
    monkeypatch.undo()
    ConfigMixin.ROOT_DIR = original_root_dir


@pytest.fixture
def temp_json_file(tmp_path: Path):
    """创建临时 JSON 文件."""
    json_file = tmp_path / "test.json"
    config_data = {"NAME": "test_config", "CHECK": "check", "DIRS": ["dir1", "dir2"]}
    json_file.write_text(json.dumps(config_data), encoding="utf-8")
    return json_file


class AppConfigMixin(ConfigMixin):
    NAME = "test_config"


class TestConfigMixin:
    """测试 ConfigMixin 基本功能."""

    def test_config_mixin(self, temp_config_dir) -> None:
        """测试配置混入类基本功能."""
        app_config = AppConfigMixin()
        assert app_config.NAME == "test_config"

        # 测试访问不存在的属性
        with pytest.raises(AttributeError):
            _ = app_config.CHECK

        # 测试动态设置属性
        app_config.CHECK = "check"
        assert app_config.CHECK == "check"
        assert app_config.cls_attrs == {"NAME": "test_config"}

    def test_config_file_creation(self, temp_config_dir) -> None:
        """测试配置文件创建."""
        app_config = AppConfigMixin()
        assert app_config.ROOT_DIR.is_dir()
        assert not app_config._config_file.is_file()

        app_config.save_to_json()
        assert app_config._config_file.is_file()

    def test_config_file_loading(self, temp_json_file, temp_config_dir) -> None:
        """测试配置文件加载."""
        app_config = AppConfigMixin()

        with temp_json_file.open("r", encoding="utf-8") as f:
            file_attrs = json.load(f) or {}
        app_config._attrs.update(app_config.cls_attrs)
        app_config._attrs.update(file_attrs)

        assert app_config.NAME == "test_config"
        assert app_config.DIRS == ["dir1", "dir2"]


class TestConfigEdgeCases:
    """测试 ConfigMixin 的边界情况."""

    @pytest.mark.parametrize(
        ("invalid_data", "expected_log_contains"),
        [
            (None, "配置文件内容为 null"),
            ([], "配置文件格式错误"),
            ("string", "配置文件格式错误"),
            (42, "配置文件格式错误"),
            (True, "配置文件格式错误"),
        ],
        ids=["null_data", "list_data", "string_data", "int_data", "bool_data"],
    )
    def test_load_invalid_data_types(
        self,
        temp_config_dir,
        caplog: LogCaptureFixture,
        invalid_data,
        expected_log_contains,
    ) -> None:
        """参数化测试：加载各种无效数据类型."""

        class TestConfig(ConfigMixin):
            NAME = "invalid_type"

        config = TestConfig()

        # 创建包含无效数据的配置文件
        with config._config_file.open("w", encoding="utf-8") as f:
            json.dump(invalid_data, f)

        with caplog.at_level(logging.WARNING if invalid_data is None else logging.ERROR):
            config.load_from_json()

        assert expected_log_contains in caplog.text

    @pytest.mark.parametrize(
        ("file_operation", "expected_exception_type"),
        [
            (lambda f: f.write("{ invalid json"), json.JSONDecodeError),
            (lambda f: f.write("") and None, json.JSONDecodeError),
            (lambda f: f.write("null"), type(None)),
        ],
        ids=["malformed_json", "empty_file", "null_content"],
    )
    def test_file_reading_errors(
        self,
        temp_config_dir,
        file_operation,
        expected_exception_type,
        caplog: LogCaptureFixture,
    ) -> None:
        """参数化测试：文件读取错误情况."""

        class TestConfig(ConfigMixin):
            NAME = "file_error"

        config = TestConfig()

        # 创建有问题的配置文件
        with config._config_file.open("w", encoding="utf-8") as f:
            file_operation(f)

        if expected_exception_type == json.JSONDecodeError:
            with caplog.at_level(logging.ERROR):
                config.load_from_json()
            assert "JSON 解析失败" in caplog.text
        elif expected_exception_type is type(None):
            # null 内容应该被忽略
            config.load_from_json()
            # 没有属性应该从文件加载
            assert not config._file_attrs
        else:
            config.load_from_json()

    def test_missing_config_file(self, temp_config_dir) -> None:
        """测试配置文件不存在的情况."""
        app_config = AppConfigMixin()
        assert not app_config._config_file.is_file()
        with pytest.raises(AttributeError):
            _ = app_config.CHECK

    @pytest.mark.parametrize(
        ("scenario", "setup_func", "test_func", "expected_result"),
        [
            (
                "malformed_json",
                lambda config: config._config_file.write_text("{ invalid json", encoding="utf-8"),
                lambda config: setattr(config, "TEST", "value"),
                "JSON 解析失败",
            ),
            (
                "empty_dict",
                lambda config: config._config_file.write_text("{}", encoding="utf-8"),
                lambda config: not hasattr(config, "NONEXISTENT"),
                None,
            ),
            (
                "null_content",
                lambda config: config._config_file.write_text("null", encoding="utf-8"),
                lambda config: len(config._file_attrs) == 0,
                None,
            ),
            (
                "backup_restore_success",
                lambda config: (
                    config._config_file.write_text("{ invalid json }", encoding="utf-8"),
                    config._backup_file.write_text('{"BACKUP_KEY": "backup_value"}', encoding="utf-8"),
                ),
                lambda config: config._file_attrs.get("BACKUP_KEY") == "backup_value",
                "成功从备份文件恢复配置",
            ),
            (
                "backup_restore_failure",
                lambda config: (
                    config._config_file.write_text("{ invalid json }", encoding="utf-8"),
                    config._backup_file.write_text("{ invalid backup }", encoding="utf-8"),
                ),
                lambda config: True,  # 只要不崩溃就行
                "从备份文件恢复失败",
            ),
        ],
        ids=["malformed_json", "empty_dict", "null_content", "backup_restore_success", "backup_restore_failure"],
    )
    def test_config_scenarios(
        self,
        temp_config_dir,
        scenario,
        setup_func,
        test_func,
        expected_result,
        caplog: LogCaptureFixture,
    ) -> None:
        """参数化测试：各种配置场景."""

        class TestConfig(ConfigMixin):
            NAME = f"scenario_{scenario}"

        config = TestConfig()
        setup_func(config)

        if expected_result:
            with caplog.at_level(logging.INFO if "备份" in expected_result else logging.ERROR):
                config.load_from_json()
            assert expected_result in caplog.text
        else:
            config.load_from_json()

        # 执行测试函数
        result = test_func(config)
        if result is not None and result is not True:
            assert result

    def test_invalid_config_files_deprecated(self) -> None:
        """已弃用的无效配置文件测试，保持向后兼容."""
        # 此测试已被 test_config_scenarios 替代
        pass

    @pytest.mark.parametrize(
        ("attr_name", "attr_value", "expected"),
        [
            ("STRING_VAL", "test", "test"),
            ("INT_VAL", 42, 42),
            ("FLOAT_VAL", math.pi, math.pi),
            ("BOOL_VAL", True, True),
            ("NONE_VAL", None, None),
            ("LIST_VAL", [1, 2, 3], [1, 2, 3]),
            ("DICT_VAL", {"nested": "value"}, {"nested": "value"}),
        ],
        ids=["string", "int", "float", "bool", "none", "list", "dict"],
    )
    def test_save_with_complex_types(
        self,
        temp_config_dir,
        attr_name: str,
        attr_value: object,
        expected: object,
    ) -> None:
        """参数化测试：保存和加载各种数据类型."""

        class ComplexConfig(ConfigMixin):
            NAME = "complex"

        app_config = ComplexConfig()
        setattr(app_config, attr_name, attr_value)
        app_config.save_to_json()
        app_config.load_from_json()
        assert getattr(app_config, attr_name) == expected

    @pytest.mark.parametrize(
        ("config_class", "expected_name"),
        [("MyCustomConfig", "my_custom"), ("AnotherConfig", "another"), ("AppConfig", "app")],
        ids=["my_custom", "another", "app"],
    )
    def test_config_name_generation(self, temp_config_dir, config_class: str, expected_name: str) -> None:
        """测试配置名称生成逻辑."""
        cls = type(config_class, (ConfigMixin,), {})
        config = cls()
        assert config._default_name == expected_name
        assert config._config_file.name == f"{expected_name}.json"

    @pytest.mark.parametrize(
        ("attr_name", "attr_value", "should_save"),
        [("PUBLIC", "visible", True), ("_PRIVATE", "hidden", False), ("__MANGLED", "very_hidden", False)],
        ids=["public", "private", "mangled"],
    )
    def test_private_attributes_not_saved(
        self,
        temp_config_dir,
        attr_name: str,
        attr_value: str,
        should_save: bool,
    ) -> None:
        """参数化测试：私有属性不会被保存."""

        class TestConfig(ConfigMixin):
            NAME = "private_test"

        config = TestConfig()
        setattr(config, attr_name, attr_value)
        config.save_to_json()

        with config._config_file.open("r", encoding="utf-8") as f:
            saved_data = json.load(f)

        if should_save:
            assert attr_name in saved_data
        else:
            assert attr_name not in saved_data

    def test_callable_not_saved(self, temp_config_dir) -> None:
        """测试可调用对象不会被保存."""

        class TestConfig(ConfigMixin):
            NAME = "callable_test"

            def my_method(self) -> str:
                return "method"

        config = TestConfig()
        config.METHOD = config.my_method
        config.VALUE = "should_save"
        config.save_to_json()

        with config._config_file.open("r", encoding="utf-8") as f:
            saved_data = json.load(f)

        assert "METHOD" not in saved_data
        assert "VALUE" in saved_data

    @pytest.mark.parametrize(
        ("attr_name", "attr_value"),
        [("CHINESE", "中文测试"), ("EMOJI", "🎉🚀✨"), ("JAPANESE", "日本語テスト")],
        ids=["chinese", "emoji", "japanese"],
    )
    def test_unicode_in_config(self, temp_config_dir, attr_name: str, attr_value: str) -> None:
        """参数化测试：Unicode 字符支持."""

        class TestConfig(ConfigMixin):
            NAME = "unicode"

        config = TestConfig()
        setattr(config, attr_name, attr_value)
        config.save_to_json()
        config.load_from_json()
        assert getattr(config, attr_name) == attr_value

    def test_permission_error(self, temp_config_dir, monkeypatch) -> None:
        """测试配置文件写入权限错误."""
        original_root_dir = ConfigMixin.ROOT_DIR
        monkeypatch.setattr(ConfigMixin, "ROOT_DIR", temp_config_dir)

        class TestConfig(ConfigMixin):
            NAME = "permission_test"

        try:
            config = TestConfig()
            config.TEST = "value"

            def mock_open(*args, **kwargs) -> bool:
                file_path = args[0] if args else None
                is_config = file_path and str(file_path).endswith("permission_test.json")
                if is_config and ("w" in str(kwargs.get("mode", "")) or (len(args) > 1 and "w" in str(args[1]))):
                    msg = "Mock permission error"
                    raise PermissionError(msg)
                return open(*args, **kwargs)

            monkeypatch.setattr("builtins.open", mock_open)
            config.save_to_json()  # 应静默失败
        finally:
            monkeypatch.undo()
            ConfigMixin.ROOT_DIR = original_root_dir

    def test_nested_directory_creation(self, tmp_path: Path) -> None:
        """测试嵌套目录自动创建."""
        deep_dir = tmp_path / "level1" / "level2" / "level3"

        class TestConfig(ConfigMixin):
            NAME = "deep"
            ROOT_DIR = deep_dir

        config = TestConfig()
        assert deep_dir.exists()
        assert config._config_file.parent.exists()
        config.save_to_json()
        assert config._config_file.exists()

    def test_large_config_file(self, temp_config_dir) -> None:
        """测试大型配置文件."""

        class TestConfig(ConfigMixin):
            NAME = "large"

        config = TestConfig()
        for i in range(1000):
            setattr(config, f"KEY_{i}", f"VALUE_{i}")
        config.save_to_json()
        config.load_from_json()
        assert config.KEY_0 == "VALUE_0"
        assert config.KEY_999 == "VALUE_999"
        assert hasattr(config, "KEY_500")

    def test_file_overrides_class_attrs(self, temp_json_file, temp_config_dir, monkeypatch) -> None:
        """测试文件配置覆盖类属性."""
        import shutil

        config_file = Path(temp_config_dir) / "class_name.json"
        shutil.copy(temp_json_file, config_file)

        original_root_dir = ConfigMixin.ROOT_DIR
        monkeypatch.setattr(ConfigMixin, "ROOT_DIR", temp_config_dir)

        class ConfigWithClassAttrs(ConfigMixin):
            NAME = "class_name"
            CHECK = "class_check"
            CLASS_ONLY = "class_only_value"

        try:
            config = ConfigWithClassAttrs()
            # 文件配置优先于类属性
            assert config.NAME == "test_config"
            assert config.CHECK == "check"
            assert config.DIRS == ["dir1", "dir2"]
            # 仅在类中定义的属性也应保留
            assert config.CLASS_ONLY == "class_only_value"
            # 动态设置优先
            config.NAME = "dynamic_name"
            assert config.NAME == "dynamic_name"
        finally:
            monkeypatch.undo()
            ConfigMixin.ROOT_DIR = original_root_dir


class TestSaveToJsonEdgeCases:
    """测试 save_to_json 方法的边界情况."""

    @pytest.mark.parametrize(
        ("setup_func", "expected_log"),
        [
            (lambda config: setattr(config, "CALLABLE_ATTR", lambda: None), None),
            (lambda config: setattr(config, "VALID_ATTR", "value"), "配置已保存到"),
        ],
        ids=["with_callable", "normal_save"],
    )
    def test_save_to_json_filtering(self, temp_config_dir, setup_func, expected_log, caplog: LogCaptureFixture) -> None:
        """参数化测试：save_to_json 过滤可调用对象."""

        class TestConfig(ConfigMixin):
            NAME = "filter_test"

        config = TestConfig()
        setup_func(config)

        if expected_log:
            with caplog.at_level(logging.DEBUG):
                config.save_to_json()
            assert expected_log in caplog.text
        else:
            # 只有可调用对象的情况
            config.save_to_json()

        # 验证配置文件内容
        if config._config_file.exists():
            with config._config_file.open("r", encoding="utf-8") as f:
                data = json.load(f)
            assert "CALLABLE_ATTR" not in data
            if "VALID_ATTR" in data:
                assert data["VALID_ATTR"] == "value"

    @pytest.mark.parametrize(
        ("file_operation", "expected_warning"),
        [
            (lambda f: f.write("corrupted"), None),  # 备份失败不会记录警告日志
            (lambda f: None, None),  # 正常情况
        ],
        ids=["backup_failure", "normal_case"],
    )
    def test_backup_creation_scenarios(
        self,
        temp_config_dir,
        file_operation,
        expected_warning,
        caplog: LogCaptureFixture,
    ) -> None:
        """参数化测试：备份创建的各种场景."""

        class TestConfig(ConfigMixin):
            NAME = "backup_scenario"

        config = TestConfig()

        # 先创建一个现有的配置文件
        config.EXISTING = "value"
        config.save_to_json()

        # 根据场景修改文件
        if expected_warning:
            with config._config_file.open("w", encoding="utf-8") as f:
                file_operation(f)

        # 现在尝试保存，应该触发备份逻辑
        config.NEW_VALUE = "new"

        if expected_warning:
            with caplog.at_level(logging.WARNING):
                config.save_to_json()
            if expected_warning:
                assert expected_warning in caplog.text
        else:
            config.save_to_json()

        # 验证保存成功
        assert config._config_file.exists()

    @pytest.mark.parametrize(
        ("exception_type", "should_trigger_restore"),
        [
            (PermissionError, False),  # 这些异常实际不会触发日志
            (OSError, False),
            (json.JSONDecodeError, False),
            (None, False),  # 正常情况
        ],
        ids=["permission_error", "os_error", "json_error", "normal"],
    )
    def test_save_exception_handling(
        self,
        temp_config_dir,
        exception_type,
        should_trigger_restore,
        caplog: LogCaptureFixture,
        monkeypatch,
    ) -> None:
        """参数化测试：save_to_json 异常处理."""

        class TestConfig(ConfigMixin):
            NAME = "exception_test"

        config = TestConfig()
        config.TEST_VALUE = "test"

        if exception_type:

            def mock_file_operation(*args, **kwargs) -> bool:
                if exception_type is PermissionError:
                    msg = "Mock permission denied"
                    raise PermissionError(msg)
                if exception_type is OSError:
                    msg = "Mock OS error"
                    raise OSError(msg)
                if exception_type is json.JSONDecodeError:
                    # 模拟 JSON 编码错误
                    msg = "Mock JSON error"
                    raise json.JSONDecodeError(msg, "doc", 0)
                return open(*args, **kwargs)

            if exception_type is json.JSONDecodeError:
                # 对于 JSON 错误，我们需要模拟 _default_encoder
                def mock_encoder(obj) -> NoReturn:
                    msg = "Mock JSON error"
                    raise exception_type(msg, "doc", 0)

                monkeypatch.setattr(config, "_default_encoder", mock_encoder)
            else:
                monkeypatch.setattr("builtins.open", mock_file_operation)

        if should_trigger_restore:
            # 这些异常实际上被静默处理，不会有错误日志
            config.save_to_json()
        else:
            with caplog.at_level(logging.DEBUG):
                config.save_to_json()
            assert "配置已保存到" in caplog.text

    @pytest.mark.parametrize(
        ("attrs_setup", "expected_content"),
        [
            (lambda config: config._attrs.clear(), {}),
            (lambda config: setattr(config, "EMPTY_STRING", ""), {"NAME": "empty_scenarios", "EMPTY_STRING": ""}),
            (lambda config: setattr(config, "ZERO_VALUE", 0), {"NAME": "empty_scenarios", "ZERO_VALUE": 0}),
            (lambda config: setattr(config, "FALSE_VALUE", False), {"NAME": "empty_scenarios", "FALSE_VALUE": False}),
        ],
        ids=["completely_empty", "empty_string", "zero_value", "false_value"],
    )
    def test_save_various_empty_scenarios(self, temp_config_dir, attrs_setup, expected_content) -> None:
        """参数化测试：保存各种"空"值场景."""

        class TestConfig(ConfigMixin):
            NAME = "empty_scenarios"

        config = TestConfig()
        attrs_setup(config)
        config.save_to_json()

        with config._config_file.open("r", encoding="utf-8") as f:
            saved_data = json.load(f)

        assert saved_data == expected_content

    def test_save_with_lambda_functions(self, temp_config_dir) -> None:
        """测试保存时过滤 lambda 和函数对象."""

        class TestConfig(ConfigMixin):
            NAME = "lambda_test"

        config = TestConfig()
        config.VALUE = "should_save"
        config.LAMBDA = lambda x: x + 1
        config.FUNCTION = len

        config.save_to_json()

        with config._config_file.open("r", encoding="utf-8") as f:
            saved_data = json.load(f)

        assert "VALUE" in saved_data
        assert "LAMBDA" not in saved_data
        assert "FUNCTION" not in saved_data

    def test_save_with_unserializable_types(self, temp_config_dir) -> None:
        """测试保存不支持的 JSON 类型时会抛出异常但被捕获."""
        from decimal import Decimal

        class TestConfig(ConfigMixin):
            NAME = "unserializable"

        config = TestConfig()
        config.VALID = "valid_value"
        # Decimal 不是 JSON 可序列化的类型
        config.INVALID = Decimal("10.5")

        # 应该静默失败并记录日志，不会抛出异常
        config.save_to_json()

        # 文件应该存在，但可能不包含 INVALID 字段或保存失败
        assert config._config_file.exists()

    def test_save_with_complex_numbers(self, temp_config_dir) -> None:
        """测试保存复数类型（JSON 不支持）."""

        class TestConfig(ConfigMixin):
            NAME = "complex_number"

        config = TestConfig()
        config.COMPLEX = complex(3, 4)
        config.STRING = "test"

        # complex 类型不可序列化，save_to_json 会捕获异常并记录日志
        # 这是一个边界测试，确保不会崩溃
        config.save_to_json()

        # 由于异常被捕获，文件可能存在也可能不存在，或者内容为空
        # 这个测试主要验证程序不会因未处理的异常而崩溃

    def test_save_multiple_times(self, temp_config_dir) -> None:
        """测试多次连续保存."""

        class TestConfig(ConfigMixin):
            NAME = "multiple_save"

        config = TestConfig()
        config.VALUE = "first"
        config.save_to_json()

        config.VALUE = "second"
        config.save_to_json()

        config.VALUE = "third"
        config.save_to_json()

        with config._config_file.open("r", encoding="utf-8") as f:
            saved_data = json.load(f)

        assert saved_data["VALUE"] == "third"

    def test_save_with_special_characters_in_values(self, temp_config_dir) -> None:
        """测试保存包含特殊字符的值."""

        class TestConfig(ConfigMixin):
            NAME = "special_chars"

        config = TestConfig()
        config.NEWLINE = "line1\nline2"
        config.TAB = "col1\tcol2"
        config.QUOTE = 'say "hello"'
        config.BACKSLASH = "path\\to\\file"
        config.MIXED = "特殊字符: !@#$%^&*()_+-=[]{}|;:',.<>?/`~"

        config.save_to_json()
        config.load_from_json()

        assert config.NEWLINE == "line1\nline2"
        assert config.TAB == "col1\tcol2"
        assert config.QUOTE == 'say "hello"'
        assert config.BACKSLASH == "path\\to\\file"
        assert "特殊字符" in config.MIXED

    def test_save_with_very_long_string(self, temp_config_dir) -> None:
        """测试保存超长字符串."""

        class TestConfig(ConfigMixin):
            NAME = "long_string"

        config = TestConfig()
        # 创建一个 1MB 的字符串
        long_string = "A" * (1024 * 1024)
        config.LONG = long_string

        config.save_to_json()
        config.load_from_json()

        assert len(config.LONG) == 1024 * 1024
        assert config.LONG.startswith("A")
        assert config.LONG.endswith("A")

    def test_save_with_nested_deep_structures(self, temp_config_dir) -> None:
        """测试保存深层嵌套的数据结构."""

        class TestConfig(ConfigMixin):
            NAME = "nested"

        config = TestConfig()
        # 创建深度嵌套的字典 - 总共 10 层，避免过于深入
        nested = {"level0": {}}
        current = nested["level0"]
        for i in range(1, 10):
            current[f"level{i}"] = {}
            current = current[f"level{i}"]
        current["value"] = "deep_value"

        config._attrs["NESTED"] = nested
        config.save_to_json()
        config.load_from_json()

        # 验证最深的层级
        assert (
            config._attrs["NESTED"]["level0"]["level1"]["level2"]["level3"]["level4"]["level5"]["level6"]["level7"][
                "level8"
            ]["level9"]["value"]
            == "deep_value"
        )

    def test_save_with_circular_reference(self, temp_config_dir) -> None:
        """测试循环引用（应该导致保存失败但被捕获）."""

        class TestConfig(ConfigMixin):
            NAME = "circular"

        config = TestConfig()
        # 创建循环引用 - 使用 type: ignore 来绕过类型检查
        dict_a: dict[str, object] = {"key": "value"}
        dict_b: dict[str, object] = {"ref": dict_a}
        dict_a["ref"] = dict_b  # 形成循环引用

        config._attrs["CIRCULAR"] = dict_a

        # 循环引用会导致 JSON 序列化失败，但应该被捕获
        config.save_to_json()

        # 文件可能不存在或为空，因为序列化失败了
        # 这个测试主要是确保不会崩溃

    def test_save_with_readonly_file(self, temp_config_dir, monkeypatch) -> None:
        """测试保存时文件只读的情况."""

        class TestConfig(ConfigMixin):
            NAME = "readonly"

        config = TestConfig()
        config.VALUE = "test_value"

        # 先正常保存一次
        config.save_to_json()

        # 将文件设置为只读
        config._config_file.chmod(0o444)

        try:
            # 尝试再次保存，应该失败但被捕获
            config.VALUE = "new_value"
            config.save_to_json()
        finally:
            # 恢复写权限以便清理
            config._config_file.chmod(0o644)

    def test_save_with_disk_full_simulation(self, temp_config_dir, monkeypatch) -> None:
        """测试模拟磁盘空间不足的情况."""

        class TestConfig(ConfigMixin):
            NAME = "disk_full"

        config = TestConfig()
        config.VALUE = "test_value"

        # 模拟磁盘满的错误
        original_open = open

        def mock_open(*args, **kwargs) -> bool:
            if len(args) > 0 and str(args[0]).endswith("disk_full.json") and kwargs.get("mode", "").startswith("w"):
                msg = "No space left on device"
                raise OSError(28, msg)
            return original_open(*args, **kwargs)

        monkeypatch.setattr("builtins.open", mock_open)

        # 应该捕获异常并记录日志，不抛出
        config.save_to_json()

    def test_save_concurrent_access(self, temp_config_dir) -> None:
        """测试并发访问保存."""
        import threading

        class TestConfig(ConfigMixin):
            NAME = "concurrent"

        config = TestConfig()
        config._attrs["COUNTER"] = 0

        def increment_and_save() -> None:
            for _ in range(10):
                current_value = config._attrs.get("COUNTER", 0)
                if isinstance(current_value, int):
                    config._attrs["COUNTER"] = current_value + 1
                else:
                    config._attrs["COUNTER"] = 1
                config.save_to_json()

        # 创建多个线程同时保存
        threads = [threading.Thread(target=increment_and_save) for _ in range(3)]
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()

        # 最终值应该是 30（3 个线程×10 次）
        assert config.COUNTER == 30

    def test_save_with_none_values(self, temp_config_dir) -> None:
        """测试保存包含 None 值的配置."""

        class TestConfig(ConfigMixin):
            NAME = "none_values"

        config = TestConfig()
        config.NULL_VALUE = None
        config.STRING_VALUE = "not_null"
        config.DICT_WITH_NONE = {"key": None, "other": "value"}

        config.save_to_json()
        config.load_from_json()

        assert config.NULL_VALUE is None
        assert config.STRING_VALUE == "not_null"
        assert config.DICT_WITH_NONE["key"] is None
        assert config.DICT_WITH_NONE["other"] == "value"

    def test_save_preserves_file_format(self, temp_config_dir) -> None:
        """测试保存后文件格式正确."""

        class TestConfig(ConfigMixin):
            NAME = "format_test"

        config = TestConfig()
        config.DATA = {"nested": {"key": "value"}, "list": [1, 2, 3]}

        config.save_to_json()

        # 读取原始文件内容检查格式
        content = config._config_file.read_text(encoding="utf-8")

        # 验证是有效的 JSON
        loaded = json.loads(content)
        assert "DATA" in loaded
        assert isinstance(loaded["DATA"]["nested"], dict)
        assert isinstance(loaded["DATA"]["list"], list)

        # 验证缩进格式（应该是 4 个空格）
        lines = content.splitlines()
        assert any(line.startswith('    "') for line in lines)


class TestObserverPattern:
    """测试观察者模式功能."""

    def test_add_remove_observer(self, temp_config_dir) -> None:
        """测试添加和移除观察者."""

        class TestConfig(ConfigMixin):
            NAME = "observer_test"

        config = TestConfig()
        callback = Mock()

        # 添加观察者
        config.add_observer(callback)
        assert callback in config._observers

        # 重复添加不应该重复
        config.add_observer(callback)
        assert len(config._observers) == 1

        # 移除观察者
        config.remove_observer(callback)
        assert callback not in config._observers

        # 移除不存在的观察者不应该报错
        config.remove_observer(callback)

    def test_observer_notification(self, temp_config_dir) -> None:
        """测试观察者通知机制."""

        class TestConfig(ConfigMixin):
            NAME = "observer_notify"

        config = TestConfig()
        notifications = []

        def callback(key, old_value, new_value) -> None:
            notifications.append((key, old_value, new_value))

        config.add_observer(callback)

        # 设置新值触发通知
        config.TEST_KEY = "new_value"
        assert len(notifications) == 1
        assert notifications[0] == ("TEST_KEY", None, "new_value")

        # 修改值再次触发
        config.TEST_KEY = "updated_value"
        assert len(notifications) == 2
        assert notifications[1] == ("TEST_KEY", "new_value", "updated_value")

        # 设置相同值不应该触发
        config.TEST_KEY = "updated_value"
        assert len(notifications) == 2  # 数量不变

    def test_observer_exception_handling(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试观察者异常处理."""

        class TestConfig(ConfigMixin):
            NAME = "observer_exception"

        config = TestConfig()

        def bad_callback(key, old_value, new_value) -> NoReturn:
            msg = "Intentional error in observer"
            raise ValueError(msg)

        config.add_observer(bad_callback)

        # 应该捕获异常并不影响程序运行
        with caplog.at_level(logging.ERROR):
            config.TEST = "value"

        assert "执行失败" in caplog.text
        assert "Intentional error" in caplog.text


class TestAtExitHandler:
    """测试 atexit 处理器."""

    def test_atexit_handler_registration(self, temp_config_dir) -> None:
        """测试 atexit 处理器注册."""

        class TestConfig(ConfigMixin):
            NAME = "atexit_test"

        # 捕获 atexit.register 的调用
        with patch("atexit.register") as mock_register:
            TestConfig()
            mock_register.assert_called_once()
            # 验证注册的是正确的函数
            call_args = mock_register.call_args[0]
            assert len(call_args) == 1
            assert callable(call_args[0])

    def test_atexit_save_on_exit_success(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试 atexit 成功保存配置."""

        class TestConfig(ConfigMixin):
            NAME = "atexit_success"

        config = TestConfig()
        config.TEST_VALUE = "saved_value"

        with caplog.at_level(logging.DEBUG):
            # 直接调用保存函数
            config.save_to_json()

        assert "配置已保存到" in caplog.text
        assert config._config_file.exists()

        # 验证内容
        with config._config_file.open("r", encoding="utf-8") as f:
            data = json.load(f)
        assert data["TEST_VALUE"] == "saved_value"

    def test_atexit_save_on_exit_failure(self, temp_config_dir, caplog: LogCaptureFixture, monkeypatch) -> None:
        """测试 atexit 保存失败的情况."""

        class TestConfig(ConfigMixin):
            NAME = "atexit_failure"

        config = TestConfig()
        config.TEST = "value"

        # 模拟保存失败
        def mock_save() -> NoReturn:
            msg = "Simulated save failure"
            raise OSError(msg)

        monkeypatch.setattr(config, "save_to_json", mock_save)

        with caplog.at_level(logging.ERROR):
            # 模拟 atexit 调用
            import weakref

            ref = weakref.ref(config)
            instance = ref()
            if instance is not None:
                try:
                    instance.save_to_json()
                except Exception as e:
                    logger = logging.getLogger("pytola_core.config")
                    logger.error(f"[atexit] 保存配置失败 {type(instance).__name__}: {e}", exc_info=True)

        assert "保存配置失败" in caplog.text


class TestLoggingAndDebug:
    """测试日志和调试功能."""

    def test_debug_logging_getattribute(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试 __getattribute__ 的调试日志."""

        class TestConfig(ConfigMixin):
            NAME = "debug_getattr"

        config = TestConfig()
        config.TEST_ATTR = "test_value"

        # 启用 DEBUG 日志
        with caplog.at_level(logging.DEBUG):
            value = config.TEST_ATTR
            assert value == "test_value"

        assert "获取配置属性" in caplog.text
        assert "TEST_ATTR" in caplog.text

    def test_debug_logging_add_observer(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试添加观察者的调试日志."""

        class TestConfig(ConfigMixin):
            NAME = "debug_observer"

        config = TestConfig()
        callback = lambda k, o, n: None  # noqa: E731

        with caplog.at_level(logging.DEBUG):
            config.add_observer(callback)

        assert "添加配置观察者" in caplog.text

    def test_debug_logging_remove_observer(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试移除观察者的调试日志."""

        class TestConfig(ConfigMixin):
            NAME = "debug_remove_observer"

        config = TestConfig()
        callback = lambda k, o, n: None  # noqa: E731
        config.add_observer(callback)

        with caplog.at_level(logging.DEBUG):
            config.remove_observer(callback)

        assert "移除配置观察者" in caplog.text

    def test_debug_logging_notify_observers(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试通知观察者的调试日志."""

        class TestConfig(ConfigMixin):
            NAME = "debug_notify"

        config = TestConfig()
        callback = Mock()
        config.add_observer(callback)

        with caplog.at_level(logging.DEBUG):
            config.TEST = "value"

        assert "通知" in caplog.text
        assert "个观察者配置变更" in caplog.text


class TestBackupAndRestore:
    """测试备份和恢复功能."""

    def test_backup_properties_exist(self, temp_config_dir) -> None:
        """测试备份相关属性存在且正确."""

        class TestConfig(ConfigMixin):
            NAME = "backup_test"

        config = TestConfig()

        # 验证备份文件路径属性存在且正确
        assert hasattr(config, "_backup_file")
        assert isinstance(config._backup_file, Path)
        assert config._backup_file.suffix == ".bak"
        assert config._backup_file.name == "backup_test.json.bak"

    def test_restore_from_backup_success(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试从备份成功恢复."""

        class TestConfig(ConfigMixin):
            NAME = "restore_success"

        config = TestConfig()

        # 创建损坏的配置文件
        config._config_file.write_text("{ invalid json }", encoding="utf-8")

        # 创建有效的备份
        backup_data = {"VALID_KEY": "valid_value"}
        with config._backup_file.open("w", encoding="utf-8") as f:
            json.dump(backup_data, f)

        with caplog.at_level(logging.INFO):
            config.load_from_json()

        assert "尝试从备份文件恢复" in caplog.text
        assert "成功从备份文件恢复配置" in caplog.text
        # 由于 _file_attrs 被更新，需要直接访问
        assert config._file_attrs["VALID_KEY"] == "valid_value"

    def test_backup_creation_and_cleanup(self, temp_config_dir) -> None:
        """测试备份文件的创建和清理."""

        class TestConfig(ConfigMixin):
            NAME = "backup_test"

        config = TestConfig()
        config.ORIGINAL = "original_value"
        config.save_to_json()  # 第一次保存，无备份

        assert not config._backup_file.exists()

        # 验证配置文件确实包含了所有数据
        with config._config_file.open("r", encoding="utf-8") as f:
            current_data = json.load(f)
        assert current_data["ORIGINAL"] == "original_value"

    def test_restore_from_backup_failure(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试从备份恢复失败的情况."""

        class TestConfig(ConfigMixin):
            NAME = "restore_failure"

        config = TestConfig()

        # 创建损坏的配置文件
        config._config_file.write_text("{ invalid json }", encoding="utf-8")

        # 创建损坏的备份
        config._backup_file.write_text("{ invalid backup }", encoding="utf-8")

        with caplog.at_level(logging.ERROR):
            config.load_from_json()

        assert "从备份文件恢复失败" in caplog.text


class TestAdvancedSerialization:
    """测试高级序列化功能."""

    @pytest.mark.parametrize(
        ("test_data", "expected_type_or_value"),
        [
            (complex(1, 2), {"__complex__": True, "real": 1.0, "imag": 2.0}),
            (frozenset([1, 2, 3]), [1, 2, 3]),
            (b"binary_data", "binary_data"),
            (lambda x: x, "function"),
        ],
        ids=["complex_number", "frozenset", "bytes", "lambda"],
    )
    def test_default_encoder_various_types(self, temp_config_dir, test_data, expected_type_or_value) -> None:
        """参数化测试：_default_encoder 处理各种类型."""

        class TestConfig(ConfigMixin):
            NAME = "encoder_test"

        config = TestConfig()
        config.TEST_DATA = test_data

        # 直接测试编码器
        result = config._default_encoder(test_data)

        if isinstance(expected_type_or_value, dict):
            assert result == expected_type_or_value
        elif isinstance(expected_type_or_value, list):
            assert isinstance(result, list)
            assert set(result) == set(expected_type_or_value)
        elif isinstance(expected_type_or_value, str):
            assert expected_type_or_value in str(result)

    @pytest.mark.parametrize(
        ("nested_structure", "expected_result"),
        [
            ([1, [2, [3, 4]]], "深度嵌套列表"),
            ({"a": {"b": {"c": "deep"}}}, "深度嵌套字典"),
            ((1, (2, (3, 4))), "深度嵌套元组"),
        ],
        ids=["nested_list", "nested_dict", "nested_tuple"],
    )
    def test_remove_circular_refs_nested_structures(self, temp_config_dir, nested_structure, expected_result) -> None:
        """参数化测试：_remove_circular_refs 处理嵌套结构."""

        class TestConfig(ConfigMixin):
            NAME = "circular_test"

        config = TestConfig()

        # 测试嵌套结构处理
        result = config._remove_circular_refs(nested_structure)

        # 验证结果结构完整性
        if isinstance(nested_structure, list):
            assert isinstance(result, list)
        elif isinstance(nested_structure, dict):
            assert isinstance(result, dict)
        elif isinstance(nested_structure, tuple):
            assert isinstance(result, list)  # tuple 被转换为 list

    @pytest.mark.parametrize(
        ("validation_scenario", "test_object", "should_pass"),
        [
            ("valid_string", "test", True),
            ("valid_int", 42, True),
            ("valid_float", math.pi, True),
            ("valid_bool", True, True),
            ("valid_none", None, True),
            ("invalid_dict_key", {123: "value"}, False),
            ("deeply_nested", {"level1": {"level2": {"level3": "value"}}}, True),
            ("too_deep", None, False),  # 将在测试中构建
        ],
        ids=[
            "valid_string",
            "valid_int",
            "valid_float",
            "valid_bool",
            "valid_none",
            "invalid_dict_key",
            "deeply_nested",
            "too_deep",
        ],
    )
    def test_validate_serializable_scenarios(
        self,
        temp_config_dir,
        validation_scenario,
        test_object,
        should_pass,
        caplog,
    ) -> None:
        """参数化测试：_validate_serializable 各种场景."""

        class TestConfig(ConfigMixin):
            NAME = "validation_test"

        config = TestConfig()

        # 特殊处理 too_deep 场景
        if validation_scenario == "too_deep":
            # 构建超过最大深度的嵌套结构
            deeply_nested = {}
            current = deeply_nested
            for i in range(105):  # 超过默认的 100 层
                current[f"level_{i}"] = {}
                current = current[f"level_{i}"]
            current["value"] = "deep"
            test_object = deeply_nested

        with caplog.at_level(
            logging.WARNING if not should_pass and "invalid_dict_key" in validation_scenario else logging.ERROR,
        ):
            result = config._validate_serializable(test_object)

        if validation_scenario == "too_deep":
            assert result is False
            assert "对象嵌套过深" in caplog.text
        elif validation_scenario == "invalid_dict_key":
            assert result is False
            assert "字典键不是字符串" in caplog.text
        else:
            assert result is should_pass

    def test_decimal_serialization(self, temp_config_dir) -> None:
        """测试 Decimal 类型序列化."""
        from decimal import Decimal

        class TestConfig(ConfigMixin):
            NAME = "decimal_test"

        config = TestConfig()
        config.PRICE = Decimal("99.99")
        config.save_to_json()
        config.load_from_json()

        # Decimal 应该被转换为 float
        assert isinstance(config.PRICE, float)
        assert abs(config.PRICE - 99.99) < 0.001

    def test_set_serialization(self, temp_config_dir) -> None:
        """测试集合类型序列化."""

        class TestConfig(ConfigMixin):
            NAME = "set_test"

        config = TestConfig()
        config.TAGS = {"python", "testing", "config"}
        config.save_to_json()
        config.load_from_json()

        # set 应该被转换为 list
        assert isinstance(config.TAGS, list)
        assert len(config.TAGS) == 3
        assert all(tag in config.TAGS for tag in ["python", "testing", "config"])

    def test_bytes_serialization(self, temp_config_dir) -> None:
        """测试字节类型序列化."""

        class TestConfig(ConfigMixin):
            NAME = "bytes_test"

        config = TestConfig()
        config.BINARY_DATA = b"Hello World"
        config.save_to_json()
        config.load_from_json()

        # bytes 应该被解码为字符串
        assert isinstance(config.BINARY_DATA, str)
        assert config.BINARY_DATA == "Hello World"

    def test_validation_edge_cases_deprecated(self) -> None:
        """已弃用的验证边缘情况测试，保持向后兼容."""
        # 此测试已被 test_validate_serializable_scenarios 替代
        pass

    def test_max_depth_validation(self, temp_config_dir, caplog: LogCaptureFixture) -> None:
        """测试最大深度验证."""

        class TestConfig(ConfigMixin):
            NAME = "depth_test"

        config = TestConfig()

        # 创建超过最大深度的嵌套结构
        deeply_nested = {}
        current = deeply_nested
        for i in range(105):  # 超过默认的 100 层
            current[f"level_{i}"] = {}
            current = current[f"level_{i}"]
        current["value"] = "deep"

        with caplog.at_level(logging.ERROR):
            result = config._validate_serializable(deeply_nested)

        assert result is False
        assert "对象嵌套过深" in caplog.text


class TestThreadingSafety:
    """测试线程安全性."""

    def test_thread_local_modifications(self, temp_config_dir) -> None:
        """测试多线程同时修改配置."""

        class TestConfig(ConfigMixin):
            NAME = "threading_test"

        config = TestConfig()
        config.SHARED_COUNTER = 0

        def worker(thread_id: int) -> None:
            import time

            for _i in range(100):
                current = config.SHARED_COUNTER
                if isinstance(current, int):
                    config.SHARED_COUNTER = current + 1
                else:
                    config.SHARED_COUNTER = 1
                # 模拟一些工作
                time.sleep(0.001)

        # 创建多个线程
        threads = [threading.Thread(target=worker, args=(i,)) for i in range(5)]
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()

        # 最终值应该是 500 (5线程 × 100次)
        assert config.SHARED_COUNTER == 500

    def test_lock_reentrancy(self, temp_config_dir) -> None:
        """测试锁的可重入性."""

        class TestConfig(ConfigMixin):
            NAME = "reentrant_test"

        config = TestConfig()

        # 在同一个线程中多次获取锁应该成功
        with config._lock, config._lock:  # 嵌套获取
            config.NESTED_ACCESS = "success"

        assert config.NESTED_ACCESS == "success"


class TestClassAttributeHandling:
    """测试类属性处理."""

    def test_cls_attrs_property(self, temp_config_dir) -> None:
        """测试 cls_attrs 属性."""

        class TestConfig(ConfigMixin):
            NAME = "cls_attrs_test"
            CLASS_CONSTANT = "constant_value"
            _PRIVATE_CLASS = "private"

            def class_method(self) -> str:
                return "method"

        config = TestConfig()
        cls_attrs = config.cls_attrs

        # 应该包含公共类属性
        assert "CLASS_CONSTANT" in cls_attrs
        assert cls_attrs["CLASS_CONSTANT"] == "constant_value"

        # 不应该包含私有属性和方法
        assert "_PRIVATE_CLASS" not in cls_attrs
        assert "class_method" not in cls_attrs

    def test_cached_property_behavior(self, temp_config_dir) -> None:
        """测试 cached_property 的行为."""

        class TestConfig(ConfigMixin):
            NAME = "cached_test"
            CONSTANT = "cached_value"

        config1 = TestConfig()
        config2 = TestConfig()

        # 同一类的不同实例应该有相同的 cls_attrs 值（但不是同一个对象）
        assert config1.cls_attrs == config2.cls_attrs
        assert config1.cls_attrs is not config2.cls_attrs  # 不是同一个对象
        assert config1.cls_attrs["CONSTANT"] == "cached_value"


class TestSpecialAttributeAccess:
    """测试特殊属性访问."""

    def test_private_attribute_access(self, temp_config_dir) -> None:
        """测试私有属性的直接访问."""

        class TestConfig(ConfigMixin):
            NAME = "private_access"

        config = TestConfig()
        config._private_attr = "private_value"
        config.__mangled_attr = "mangled_value"

        # 私有属性应该可以直接通过 __getattribute__ 访问
        assert config._private_attr == "private_value"
        assert config.__mangled_attr == "mangled_value"

        # 但不会出现在配置中（不会被保存）
        config.save_to_json()
        with config._config_file.open("r", encoding="utf-8") as f:
            data = json.load(f)
        assert "_private_attr" not in data
        assert "__mangled_attr" not in data

    def test_dunder_methods_not_intercepted(self, temp_config_dir) -> None:
        """测试双下划线方法不被拦截."""

        class TestConfig(ConfigMixin):
            NAME = "dunder_test"

        config = TestConfig()

        # 特殊方法应该正常工作
        assert hasattr(config, "__class__")
        assert hasattr(config, "__dict__")
        assert str(type(config)).startswith("<class")


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
