from __future__ import annotations

from typing import Any, Dict, Mapping, Optional, Protocol, runtime_checkable

from xfintech.connect.common.error import ConnectRefKeyError


@runtime_checkable
class ConnectRefLike(Protocol):
    """
    描述:
    - 连接引用接口协议。
    - 定义连接引用对象的基本属性和方法。
    - 用于表示存储位置的元数据。

    属性:
    - location: 存储位置的字符串表示。
    - kind: 可选的连接类型标识（如 's3', 'macos'）。

    方法:
    - describe(): 返回引用的描述信息字典。
    - to_dict(): 将引用转换为字典格式。
    """

    location: str
    kind: Optional[str]

    def describe(self) -> Dict[str, Optional[str]]: ...
    def to_dict(self) -> Dict[str, Optional[str]]: ...


class ConnectRef(ConnectRefLike):
    """
    描述:
    - 连接引用数据类，表示存储对象的位置引用。
    - 包含位置信息和可选的类型标识。
    - 支持从字典创建和序列化为字典。
    - 实现了相等性比较和哈希方法。

    属性:
    - location: 存储位置字符串。
    - kind: 连接类型（如 's3', 'macos' 等）。

    例子:
    ```python
        from xfintech.connect.common.connectref import ConnectRef

        # 创建引用
        ref = ConnectRef(location="s3://bucket/key", kind="s3")

        # 从字典创建
        ref2 = ConnectRef.from_dict({"location": "path/to/file", "kind": "macos"})

        # 序列化
        data = ref.to_dict()

        # 比较
        assert ref == ConnectRef(location="s3://bucket/key", kind="s3")
    ```
    """

    @classmethod
    def from_dict(
        cls,
        data: Mapping[str, Any],
    ) -> "ConnectRef":
        if "location" not in data:
            msg = "'location' is required in data"
            raise ConnectRefKeyError(msg)

        location = str(data["location"])
        kind = data.get("kind")
        return cls(
            location=location,
            kind=kind,
        )

    def __init__(
        self,
        location: str,
        kind: Optional[str] = None,
    ) -> None:
        self.location = location
        self.kind = kind

    def __str__(self) -> str:
        return f"{self.location}"

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(location={self.location}, kind={self.kind})"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, ConnectRef):
            return False
        return self.location == other.location and self.kind == other.kind

    def __hash__(self) -> int:
        return hash((self.location, self.kind))

    def to(self, rel: str) -> ConnectRef:
        old = self.location.rstrip("/")
        rel = rel.lstrip("/")
        new = f"{old}/{rel}"
        return ConnectRef(
            location=new,
            kind=self.kind,
        )

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

    def to_dict(self) -> Dict[str, Optional[str]]:
        result: Dict[str, Optional[str]] = {"location": self.location}
        if self.kind is not None:
            result["kind"] = self.kind
        return result
