from __future__ import annotations

from typing import Any, Dict, Mapping, Optional

from xfintech.connect.common.connect import ConnectLike
from xfintech.connect.common.connectref import ConnectRef, ConnectRefLike
from xfintech.serde.common.dataformat import DataFormat
from xfintech.serde.common.deserialiserlike import DeserialiserLike
from xfintech.serde.common.serialiserlike import SerialiserLike


class Artifact:
    """
    描述:
        数据制品类，用于管理数据引用、内容和元数据

    属性:
        ref: ConnectRefLike, 数据引用对象
        data: Any, 数据内容（可选）
        meta: Dict[str, Any], 元数据（可选）

    方法:
        from_dict(data): 从字典创建Artifact实例
        write(connect, serialiser, format): 写入数据到存储
        read(connect, deserialiser, format): 从存储读取数据
        describe(): 返回制品描述信息
        to_dict(): 转换为字典格式

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

        # 创建制品
        ref = ConnectRef(location="s3://bucket/data.json")
        artifact = Artifact(ref=ref, data={"key": "value"})

        # 写入数据
        artifact.write(connect=s3_connect, serialiser=json_serialiser, format="json")

        # 读取数据
        artifact2 = Artifact(ref=ref)
        artifact2.read(connect=s3_connect, deserialiser=json_deserialiser, format="json")
        ```
    """

    @classmethod
    def from_dict(
        cls,
        data: Mapping[str, Any],
    ) -> "Artifact":
        return cls(
            ref=data["ref"],
            data=data.get("data"),
            meta=data.get("meta"),
        )

    def __init__(
        self,
        ref: ConnectRefLike | Dict[str, str],
        data: Optional[Any] = None,
        meta: Optional[Mapping[str, Any]] = None,
    ) -> None:
        self.ref = self._resolve_ref(ref)
        self.data = data
        self.meta = self._resolve_meta(meta)

    def _resolve_ref(
        self,
        ref: ConnectRefLike | Dict[str, str],
    ) -> ConnectRefLike:
        if isinstance(ref, ConnectRef):
            return ref
        if isinstance(ref, dict):
            return ConnectRef.from_dict(ref)
        msg = f"Invalid type for ref: {type(ref)}, expected ConnectRefLike or dict"
        raise TypeError(msg)

    def _resolve_meta(
        self,
        meta: Optional[Mapping[str, Any]],
    ) -> Optional[Dict[str, Any]]:
        if meta is None:
            return None
        return dict(meta)

    def __str__(self) -> str:
        return str(self.ref)

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

    def write(
        self,
        connect: ConnectLike,
        serialiser: Optional[SerialiserLike] = None,
        format: Optional[DataFormat | str] = None,
        **kwargs: Any,
    ) -> None:
        if self.data is None:
            raise ValueError("No data to write")
        try:
            if serialiser is not None and format is not None:
                payload = serialiser.serialise(
                    self.data,
                    format=format,
                    **kwargs,
                )
            else:
                payload = self.data

            self.ref = connect.put_object(
                data=payload,
                location=self.ref.location,
                **kwargs,
            )
        except ValueError:
            raise
        except Exception as e:
            raise Exception(f"Failed to write artifact: {e}") from e

    def read(
        self,
        connect: ConnectLike,
        deserialiser: Optional[DeserialiserLike] = None,
        format: Optional[DataFormat | str] = None,
        **kwargs: Any,
    ) -> "Artifact":
        try:
            payload = connect.get_object(
                location=self.ref.location,
                **kwargs,
            )
            if deserialiser is not None and format is not None:
                self.data = deserialiser.deserialise(
                    payload,
                    format=format,
                    **kwargs,
                )
            else:
                self.data = payload
            return self
        except Exception as e:
            raise Exception(f"Failed to read artifact: {e}") from e

    def describe(self) -> Dict[str, Any]:
        if hasattr(self.ref, "describe") and callable(self.ref.describe):
            result = {"ref": self.ref.describe()}
        else:
            result = {"ref": str(self.ref)}

        if self.meta is not None:
            result["meta"] = self.meta
        if self.data is not None:
            result["data_type"] = str(type(self.data).__name__)
        return result

    def to_dict(self) -> Dict[str, Any]:
        if hasattr(self.ref, "to_dict") and callable(self.ref.to_dict):
            result = {"ref": self.ref.to_dict()}
        else:
            result = {"ref": str(self.ref)}

        if self.meta is not None:
            result["meta"] = self.meta
        if self.data is not None:
            result["data"] = self.data
        return result
