"""
描述:
- ConnectRefLike 协议和 ConnectRef 类的单元测试。
- 测试协议的结构、ConnectRef 的功能和边界情况。
"""

from __future__ import annotations

from typing import Dict, Optional

import pytest

from xfintech.connect.common.connectref import ConnectRef, ConnectRefLike
from xfintech.connect.common.error import ConnectRefKeyError

# =============================================================================
# ConnectRefLike Protocol Tests
# =============================================================================


def test_connectreflike_is_protocol():
    """测试 ConnectRefLike 是一个协议类"""
    from typing import Protocol

    assert issubclass(ConnectRefLike.__class__, type(Protocol))


def test_connectreflike_is_runtime_checkable():
    """测试 ConnectRefLike 是运行时可检查的"""
    # Check for protocol marker attributes that exist in Python 3.8+
    assert hasattr(ConnectRefLike, "_is_protocol") or hasattr(ConnectRefLike, "__protocol_attrs__")


def test_connectreflike_has_required_attributes():
    """测试 ConnectRefLike 定义了必需的属性"""

    annotations = ConnectRefLike.__annotations__
    assert "location" in annotations
    assert "kind" in annotations


def test_connectreflike_has_describe_method():
    """测试 ConnectRefLike 定义了 describe 方法"""
    assert hasattr(ConnectRefLike, "describe")


def test_connectreflike_has_to_dict_method():
    """测试 ConnectRefLike 定义了 to_dict 方法"""
    assert hasattr(ConnectRefLike, "to_dict")


def test_valid_connectreflike_implementation():
    """测试有效的 ConnectRefLike 实现"""

    class MyRef:
        def __init__(self):
            self.location = "test"
            self.kind = "test"

        def describe(self) -> Dict[str, Optional[str]]:
            return {"location": self.location}

        def to_dict(self) -> Dict[str, Optional[str]]:
            return {"location": self.location, "kind": self.kind}

    ref = MyRef()
    assert isinstance(ref, ConnectRefLike)


def test_invalid_connectreflike_missing_location():
    """测试缺少 location 属性的实现"""

    class InvalidRef:
        def __init__(self):
            self.kind = "test"

        def describe(self) -> Dict[str, Optional[str]]:
            return {}

        def to_dict(self) -> Dict[str, Optional[str]]:
            return {}

    ref = InvalidRef()
    assert not isinstance(ref, ConnectRefLike)


# =============================================================================
# ConnectRef Initialization Tests
# =============================================================================


def test_connectref_init_with_location_only():
    """测试仅使用 location 初始化"""
    ref = ConnectRef(location="test_location")
    assert ref.location == "test_location"
    assert ref.kind is None


def test_connectref_init_with_location_and_kind():
    """测试使用 location 和 kind 初始化"""
    ref = ConnectRef(location="test_location", kind="s3")
    assert ref.location == "test_location"
    assert ref.kind == "s3"


def test_connectref_init_empty_location():
    """测试空字符串 location"""
    ref = ConnectRef(location="")
    assert ref.location == ""
    assert ref.kind is None


def test_connectref_init_none_kind():
    """测试显式 None 的 kind"""
    ref = ConnectRef(location="loc", kind=None)
    assert ref.location == "loc"
    assert ref.kind is None


def test_connectref_is_connectreflike():
    """测试 ConnectRef 是 ConnectRefLike 的实例"""
    ref = ConnectRef(location="test")
    assert isinstance(ref, ConnectRefLike)


# =============================================================================
# ConnectRef.from_dict Tests
# =============================================================================


def test_from_dict_with_location_only():
    """测试从仅包含 location 的字典创建"""
    data = {"location": "test_loc"}
    ref = ConnectRef.from_dict(data)
    assert ref.location == "test_loc"
    assert ref.kind is None


def test_from_dict_with_location_and_kind():
    """测试从包含 location 和 kind 的字典创建"""
    data = {"location": "test_loc", "kind": "s3"}
    ref = ConnectRef.from_dict(data)
    assert ref.location == "test_loc"
    assert ref.kind == "s3"


def test_from_dict_missing_location_raises_error():
    """测试缺少 location 键抛出错误"""
    data = {"kind": "s3"}
    with pytest.raises(ConnectRefKeyError) as excinfo:
        ConnectRef.from_dict(data)
    assert "'location' is required in data" in str(excinfo.value)


def test_from_dict_empty_dict_raises_error():
    """测试空字典抛出错误"""
    with pytest.raises(ConnectRefKeyError):
        ConnectRef.from_dict({})


def test_from_dict_with_extra_keys():
    """测试包含额外键的字典"""
    data = {"location": "loc", "kind": "s3", "extra": "ignored"}
    ref = ConnectRef.from_dict(data)
    assert ref.location == "loc"
    assert ref.kind == "s3"


def test_from_dict_none_kind():
    """测试 kind 为 None 的字典"""
    data = {"location": "loc", "kind": None}
    ref = ConnectRef.from_dict(data)
    assert ref.location == "loc"
    assert ref.kind is None


def test_from_dict_location_type_conversion():
    """测试 location 类型转换"""
    data = {"location": 123}
    ref = ConnectRef.from_dict(data)
    assert ref.location == "123"
    assert isinstance(ref.location, str)


def test_from_dict_with_mapping_type():
    """测试使用 Mapping 类型"""
    from collections import OrderedDict

    data = OrderedDict([("location", "test"), ("kind", "macos")])
    ref = ConnectRef.from_dict(data)
    assert ref.location == "test"
    assert ref.kind == "macos"


# =============================================================================
# ConnectRef String Representation Tests
# =============================================================================


def test_str_returns_location():
    """测试 __str__ 返回 location"""
    ref = ConnectRef(location="test_location")
    assert str(ref) == "test_location"


def test_str_with_kind():
    """测试带 kind 的 __str__"""
    ref = ConnectRef(location="test_location", kind="s3")
    assert str(ref) == "test_location"


def test_repr_format():
    """测试 __repr__ 格式"""
    ref = ConnectRef(location="test_location", kind="s3")
    repr_str = repr(ref)
    assert "ConnectRef" in repr_str
    assert "location=test_location" in repr_str
    assert "kind=s3" in repr_str


def test_repr_without_kind():
    """测试没有 kind 的 __repr__"""
    ref = ConnectRef(location="test_location")
    repr_str = repr(ref)
    assert "ConnectRef" in repr_str
    assert "location=test_location" in repr_str
    assert "kind=None" in repr_str


def test_repr_can_be_evaluated():
    """测试 __repr__ 可以被评估（手动检查）"""
    ref = ConnectRef(location="test", kind="s3")
    repr_str = repr(ref)
    # Manual check - repr shows the structure clearly
    assert "test" in repr_str
    assert "s3" in repr_str


# =============================================================================
# ConnectRef Equality Tests
# =============================================================================


def test_equality_same_location_and_kind():
    """测试相同 location 和 kind 的相等性"""
    ref1 = ConnectRef(location="test", kind="s3")
    ref2 = ConnectRef(location="test", kind="s3")
    assert ref1 == ref2


def test_equality_same_location_no_kind():
    """测试相同 location 无 kind 的相等性"""
    ref1 = ConnectRef(location="test")
    ref2 = ConnectRef(location="test")
    assert ref1 == ref2


def test_inequality_different_location():
    """测试不同 location 的不相等性"""
    ref1 = ConnectRef(location="test1", kind="s3")
    ref2 = ConnectRef(location="test2", kind="s3")
    assert ref1 != ref2


def test_inequality_different_kind():
    """测试不同 kind 的不相等性"""
    ref1 = ConnectRef(location="test", kind="s3")
    ref2 = ConnectRef(location="test", kind="macos")
    assert ref1 != ref2


def test_inequality_one_has_kind():
    """测试一个有 kind 一个无 kind 的不相等性"""
    ref1 = ConnectRef(location="test", kind="s3")
    ref2 = ConnectRef(location="test")
    assert ref1 != ref2


def test_equality_with_non_connectref():
    """测试与非 ConnectRef 对象的比较"""
    ref = ConnectRef(location="test")
    assert ref != "test"
    assert ref != 123
    assert ref is not None
    assert ref != {"location": "test"}


def test_equality_reflexive():
    """测试相等性的自反性"""
    ref = ConnectRef(location="test", kind="s3")
    assert ref == ref


def test_equality_symmetric():
    """测试相等性的对称性"""
    ref1 = ConnectRef(location="test", kind="s3")
    ref2 = ConnectRef(location="test", kind="s3")
    assert ref1 == ref2
    assert ref2 == ref1


def test_equality_transitive():
    """测试相等性的传递性"""
    ref1 = ConnectRef(location="test", kind="s3")
    ref2 = ConnectRef(location="test", kind="s3")
    ref3 = ConnectRef(location="test", kind="s3")
    assert ref1 == ref2
    assert ref2 == ref3
    assert ref1 == ref3


# =============================================================================
# ConnectRef Hash Tests
# =============================================================================


def test_hash_same_values():
    """测试相同值的哈希相同"""
    ref1 = ConnectRef(location="test", kind="s3")
    ref2 = ConnectRef(location="test", kind="s3")
    assert hash(ref1) == hash(ref2)


def test_hash_different_values():
    """测试不同值的哈希可能不同"""
    ref1 = ConnectRef(location="test1", kind="s3")
    ref2 = ConnectRef(location="test2", kind="s3")
    # Hashes might collide, but typically different
    # Just test they are hashable
    assert isinstance(hash(ref1), int)
    assert isinstance(hash(ref2), int)


def test_can_use_in_set():
    """测试可以在集合中使用"""
    ref1 = ConnectRef(location="test1", kind="s3")
    ref2 = ConnectRef(location="test2", kind="s3")
    ref3 = ConnectRef(location="test1", kind="s3")

    ref_set = {ref1, ref2, ref3}
    assert len(ref_set) == 2  # ref1 and ref3 are equal


def test_can_use_as_dict_key():
    """测试可以作为字典键使用"""
    ref1 = ConnectRef(location="test1", kind="s3")
    ref2 = ConnectRef(location="test2", kind="s3")

    data = {ref1: "value1", ref2: "value2"}
    assert data[ref1] == "value1"
    assert data[ref2] == "value2"


def test_hash_consistent_with_equality():
    """测试哈希与相等性一致"""
    ref1 = ConnectRef(location="test", kind="s3")
    ref2 = ConnectRef(location="test", kind="s3")

    if ref1 == ref2:
        assert hash(ref1) == hash(ref2)


# =============================================================================
# ConnectRef.describe Tests
# =============================================================================


def test_describe_with_location_only():
    """测试仅 location 的描述"""
    ref = ConnectRef(location="test_location")
    result = ref.describe()
    assert result == {"location": "test_location"}


def test_describe_with_location_and_kind():
    """测试带 kind 的描述"""
    ref = ConnectRef(location="test_location", kind="s3")
    result = ref.describe()
    assert result == {"location": "test_location", "kind": "s3"}


def test_describe_kind_none_not_included():
    """测试 kind 为 None 时不包含在描述中"""
    ref = ConnectRef(location="test", kind=None)
    result = ref.describe()
    assert "kind" not in result
    assert result == {"location": "test"}


def test_describe_returns_dict():
    """测试 describe 返回字典类型"""
    ref = ConnectRef(location="test")
    result = ref.describe()
    assert isinstance(result, dict)


def test_describe_empty_kind_string():
    """测试空字符串 kind"""
    ref = ConnectRef(location="test", kind="")
    result = ref.describe()
    # Empty string is falsy, so not included
    assert "kind" not in result


# =============================================================================
# ConnectRef.to_dict Tests
# =============================================================================


def test_to_dict_with_location_only():
    """测试仅 location 的字典转换"""
    ref = ConnectRef(location="test_location")
    result = ref.to_dict()
    assert result == {"location": "test_location"}


def test_to_dict_with_location_and_kind():
    """测试带 kind 的字典转换"""
    ref = ConnectRef(location="test_location", kind="s3")
    result = ref.to_dict()
    assert result == {"location": "test_location", "kind": "s3"}


def test_to_dict_kind_none_not_included():
    """测试 kind 为 None 时不包含"""
    ref = ConnectRef(location="test")
    result = ref.to_dict()
    assert "kind" not in result


def test_to_dict_returns_dict():
    """测试 to_dict 返回字典类型"""
    ref = ConnectRef(location="test")
    result = ref.to_dict()
    assert isinstance(result, dict)


def test_to_dict_roundtrip():
    """测试 to_dict 和 from_dict 往返"""
    original = ConnectRef(location="test_loc", kind="s3")
    data = original.to_dict()
    restored = ConnectRef.from_dict(data)
    assert original == restored


def test_to_dict_roundtrip_no_kind():
    """测试无 kind 的往返"""
    original = ConnectRef(location="test_loc")
    data = original.to_dict()
    restored = ConnectRef.from_dict(data)
    assert original == restored


# =============================================================================
# ConnectRef Edge Cases
# =============================================================================


def test_special_characters_in_location():
    """测试 location 中的特殊字符"""
    special_locs = [
        "s3://bucket/key/with/slashes",
        "/local/path/with spaces",
        "~/user/home/path",
        "path\\with\\backslashes",
        "path-with-dashes",
        "path_with_underscores",
        "path.with.dots",
    ]

    for loc in special_locs:
        ref = ConnectRef(location=loc)
        assert ref.location == loc


def test_unicode_in_location():
    """测试 location 中的 Unicode 字符"""
    ref = ConnectRef(location="路径/文件.txt", kind="中文")
    assert ref.location == "路径/文件.txt"
    assert ref.kind == "中文"


def test_very_long_location():
    """测试非常长的 location"""
    long_loc = "a" * 10000
    ref = ConnectRef(location=long_loc)
    assert ref.location == long_loc
    assert len(ref.location) == 10000


def test_empty_string_location():
    """测试空字符串 location"""
    ref = ConnectRef(location="")
    assert ref.location == ""
    assert str(ref) == ""


def test_whitespace_only_location():
    """测试仅空格的 location"""
    ref = ConnectRef(location="   ")
    assert ref.location == "   "


def test_newline_in_location():
    """测试 location 中的换行符"""
    ref = ConnectRef(location="line1\nline2")
    assert ref.location == "line1\nline2"


# =============================================================================
# Real-World Usage Tests
# =============================================================================


def test_s3_reference():
    """测试 S3 引用"""
    ref = ConnectRef(location="s3://my-bucket/path/to/object.parquet", kind="s3")
    assert ref.location == "s3://my-bucket/path/to/object.parquet"
    assert ref.kind == "s3"


def test_local_file_reference():
    """测试本地文件引用"""
    ref = ConnectRef(location="/Users/username/data/file.csv", kind="macos")
    assert ref.location == "/Users/username/data/file.csv"
    assert ref.kind == "macos"


def test_relative_path_reference():
    """测试相对路径引用"""
    ref = ConnectRef(location="./data/file.json", kind="local")
    assert ref.location == "./data/file.json"


def test_http_url_reference():
    """测试 HTTP URL 引用"""
    ref = ConnectRef(location="https://example.com/data/file.json", kind="http")
    assert ref.location == "https://example.com/data/file.json"
    assert ref.kind == "http"


def test_multiple_references_in_list():
    """测试列表中的多个引用"""
    refs = [
        ConnectRef(location="loc1", kind="s3"),
        ConnectRef(location="loc2", kind="macos"),
        ConnectRef(location="loc3", kind="s3"),
    ]

    assert len(refs) == 3
    assert all(isinstance(ref, ConnectRef) for ref in refs)


def test_reference_serialization_workflow():
    """测试引用的序列化工作流"""
    # Create
    ref = ConnectRef(location="s3://bucket/key", kind="s3")

    # Serialize
    data = ref.to_dict()
    assert isinstance(data, dict)

    # Store (simulate JSON)
    import json

    json_str = json.dumps(data)

    # Restore
    restored_data = json.loads(json_str)
    restored_ref = ConnectRef.from_dict(restored_data)

    # Verify
    assert restored_ref == ref


def test_reference_in_metadata():
    """测试引用在元数据中的使用"""
    metadata = {
        "source": ConnectRef(location="s3://source/data", kind="s3"),
        "destination": ConnectRef(location="/local/output", kind="macos"),
    }

    assert isinstance(metadata["source"], ConnectRef)
    assert isinstance(metadata["destination"], ConnectRef)
    assert metadata["source"].kind == "s3"
    assert metadata["destination"].kind == "macos"
