import hashlib
import json
import time
from datetime import datetime
from pathlib import Path
from typing import Optional, List

from app import schemas
from app.core.cache import cached
from app.core.config import settings, global_vars
from app.log import logger
from app.modules.filemanager.storages import StorageBase, transfer_process
from app.schemas.exception import OperationInterrupted
from app.schemas.types import StorageSchema
from app.utils.http import RequestUtils
from app.utils.singleton import WeakSingleton
from app.utils.url import UrlUtils


# OpenList/AList 在 per_page<=0 时会退回后端默认 200，显式指定最大页大小避免大目录被截断。
OPENLIST_MAX_LIST_PAGE_SIZE = 500


class Alist(StorageBase, metaclass=WeakSingleton):
    """
    Openlist相关操作

    API 文档：https://fox.oplist.org/
    """

    # 存储类型
    schema = StorageSchema.Alist

    # 支持的整理方式
    transtype = {
        "copy": "复制",
        "move": "移动",
    }

    # 快照检查目录修改时间
    snapshot_check_folder_modtime = settings.OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME

    def __init__(self):
        super().__init__()

    def init_storage(self):
        """
        初始化
        """
        self.__generate_token.cache_clear()  # noqa

    def _delay_get_item(
        self, path: Path, /, refresh: bool = False
    ) -> Optional[schemas.FileItem]:
        """
        自动延迟重试 get_item 模块

        :param path: 文件路径
        :param refresh: 是否刷新
        :return: 文件项
        """
        for _ in range(2):
            time.sleep(2)
            fileitem = self.get_item(path=path, refresh=refresh)
            if fileitem:
                return fileitem
        return None

    def __build_transfer_item(
            self, source_item: schemas.FileItem, target_path: Path
    ) -> schemas.FileItem:
        """
        根据目标路径构造文件项，用于 OpenList 操作成功但元数据短时间不可见的场景。
        目录项路径需要遵循 FileItem 以斜杠结尾的约定。
        """
        target_path_str = target_path.as_posix()
        if source_item.type == "dir" and not target_path_str.endswith("/"):
            target_path_str = f"{target_path_str}/"

        return schemas.FileItem(
            storage=self.schema.value,
            type=source_item.type,
            path=target_path_str,
            name=target_path.name,
            basename=target_path.stem,
            extension=target_path.suffix[1:] if source_item.type != "dir" else None,
            size=getattr(source_item, "size", None),
            modify_time=getattr(source_item, "modify_time", None),
            thumbnail=getattr(source_item, "thumbnail", None),
        )

    @property
    def __get_base_url(self) -> str:
        """
        获取基础URL
        """
        url = self.get_conf().get("url")
        if url is None:
            return ""
        return UrlUtils.standardize_base_url(self.get_conf().get("url"))

    def __get_api_url(self, path: str) -> str:
        """
        获取API URL

        :param path: API路径
        :return: API URL
        """
        return UrlUtils.adapt_request_url(self.__get_base_url, path)

    @property
    def __get_valuable_toke(self) -> str:
        """
        获取一个可用的token
        如果设置永久令牌则返回永久令牌
        否则使用账号密码生成临时令牌
        """
        return self.__generate_token()

    @cached(maxsize=1, ttl=60 * 60 * 24 * 2 - 60 * 5, skip_empty=True)
    def __generate_token(self) -> str:
        """
        如果设置永久令牌则返回永久令牌，否则使用账号密码生成一个临时 token
        缓存2天，提前5分钟更新
        """
        conf = self.get_conf()
        token = conf.get("token")
        if token:
            return str(token)
        resp = RequestUtils(headers={"Content-Type": "application/json"}).post_res(
            self.__get_api_url("/api/auth/login"),
            data=json.dumps(
                {
                    "username": conf.get("username"),
                    "password": conf.get("password"),
                }
            ),
        )
        """
        {
            "username": "{{alist_username}}",
            "password": "{{alist_password}}"
        }
        ======================================
        {
            "code": 200,
            "message": "success",
            "data": {
                "token": "abcd"
            }
        }
        """

        if resp is None:
            logger.warning("【OpenList】请求登录失败，无法连接alist服务")
            return ""

        if resp.status_code != 200:
            logger.warning(
                f"【OpenList】更新令牌请求发送失败，状态码：{resp.status_code}"
            )
            return ""

        result = resp.json()

        if result["code"] != 200:
            logger.critical(f"【OpenList】更新令牌，错误信息：{result['message']}")
            return ""

        logger.debug("【OpenList】AList获取令牌成功")
        return result["data"]["token"]

    def __get_header_with_token(self) -> dict:
        """
        获取带有token的header
        """
        return {"Authorization": self.__get_valuable_toke}

    def check(self) -> bool:
        """
        检查存储是否可用
        """
        return True if self.__generate_token() else False

    def list(
        self,
        fileitem: schemas.FileItem,
        password: Optional[str] = "",
        page: int = 1,
        per_page: int = 0,
        refresh: bool = False,
    ) -> List[schemas.FileItem]:
        """
        浏览文件
        :param fileitem: 文件项
        :param password: 路径密码
        :param page: 页码
        :param per_page: 每页数量
        :param refresh: 是否刷新
        :return: 文件列表
        """
        if fileitem.type == "file":
            item = self.get_item(Path(fileitem.path))
            if item:
                return [item]
            return []
        items = []
        current_page = page
        auto_page = per_page <= 0
        effective_per_page = OPENLIST_MAX_LIST_PAGE_SIZE if auto_page else per_page
        while True:
            resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
                self.__get_api_url("/api/fs/list"),
                json={
                    "path": fileitem.path,
                    "password": password,
                    "page": current_page,
                    "per_page": effective_per_page,
                    "refresh": refresh,
                },
            )
            """
            {
                "path": "/t",
                "password": "",
                "page": 1,
                "per_page": 0,
                "refresh": false
            }
            ======================================
            {
                "code": 200,
                "message": "success",
                "data": {
                    "content": [
                    {
                        "name": "Alist V3.md",
                        "size": 1592,
                        "is_dir": false,
                        "modified": "2024-05-17T13:47:55.4174917+08:00",
                        "created": "2024-05-17T13:47:47.5725906+08:00",
                        "sign": "",
                        "thumb": "",
                        "type": 4,
                        "hashinfo": "null",
                        "hash_info": null
                    }
                    ],
                    "total": 1,
                    "readme": "",
                    "header": "",
                    "write": true,
                    "provider": "Local"
                }
            }
            """

            if resp is None:
                logger.warn(
                    f"【OpenList】请求获取目录 {fileitem.path} 的文件列表失败，无法连接alist服务"
                )
                return []
            if resp.status_code != 200:
                logger.warn(
                    f"【OpenList】请求获取目录 {fileitem.path} 的文件列表失败，状态码：{resp.status_code}"
                )
                return []

            result = resp.json()

            if result["code"] != 200:
                logger.warn(
                    f"【OpenList】获取目录 {fileitem.path} 的文件列表失败，错误信息：{result['message']}"
                )
                return []

            page_data = result["data"]
            page_content = page_data.get("content") or []
            items.extend(
                [
                    schemas.FileItem(
                        storage=self.schema.value,
                        type="dir" if item["is_dir"] else "file",
                        path=(Path(fileitem.path) / item["name"]).as_posix()
                        + ("/" if item["is_dir"] else ""),
                        name=item["name"],
                        basename=Path(item["name"]).stem,
                        extension=Path(item["name"]).suffix[1:] if not item["is_dir"] else None,
                        size=item["size"] if not item["is_dir"] else None,
                        modify_time=self.__parse_timestamp(item["modified"]),
                        thumbnail=item["thumb"],
                    )
                    for item in page_content
                ]
            )

            if not auto_page:
                return items

            total = page_data.get("filtered_total") or page_data.get("total") or 0
            pages_total = page_data.get("pages_total") or 0
            has_more = page_data.get("has_more")
            if not page_content or len(items) >= total:
                return items
            if has_more is False or (pages_total and current_page >= pages_total):
                return items

            current_page += 1

    def create_folder(
        self, fileitem: schemas.FileItem, name: str
    ) -> Optional[schemas.FileItem]:
        """
        创建目录
        :param fileitem: 父目录
        :param name: 目录名
        :return: 目录项
        """
        path = Path(fileitem.path) / name
        resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
            self.__get_api_url("/api/fs/mkdir"),
            json={"path": path.as_posix()},
        )
        """
        {
            "path": "/tt"
        }
        ======================================
        {
            "code": 200,
            "message": "success",
            "data": null
        }
        """
        if resp is None:
            logger.warn(f"【OpenList】请求创建目录 {path} 失败，无法连接alist服务")
            return None
        if resp.status_code != 200:
            logger.warn(
                f"【OpenList】请求创建目录 {path} 失败，状态码：{resp.status_code}"
            )
            return None

        result = resp.json()
        if result["code"] != 200:
            logger.warn(
                f"【OpenList】创建目录 {path} 失败，错误信息：{result['message']}"
            )
            return None

        return self._delay_get_item(
            path, refresh=True
        ) or self.__build_transfer_item(
            schemas.FileItem(
                storage=self.schema.value,
                type="dir",
                path=fileitem.path,
                name=name,
                basename=Path(name).stem,
            ),
            path,
        )

    def get_folder(self, path: Path) -> Optional[schemas.FileItem]:
        """
        获取目录，如目录不存在则创建

        :param path: 目录路径
        :return: 目录项
        """
        folder = self.get_item(path)
        if folder:
            return folder
        if not folder:
            folder = self.create_folder(
                schemas.FileItem(
                    storage=self.schema.value,
                    type="dir",
                    path=path.parent.as_posix(),
                    name=path.name,
                    basename=path.stem,
                ),
                path.name,
            )
        return folder

    def get_item(
        self,
        path: Path,
        password: Optional[str] = "",
        page: int = 1,
        per_page: int = 0,
        refresh: bool = False,
    ) -> Optional[schemas.FileItem]:
        """
        获取文件或目录，不存在返回None
        :param path: 文件路径
        :param password: 路径密码
        :param page: 页码
        :param per_page: 每页数量
        :param refresh: 是否刷新
        :return: 文件项
        """
        resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
            self.__get_api_url("/api/fs/get"),
            json={
                "path": path.as_posix(),
                "password": password,
                "page": page,
                "per_page": per_page,
                "refresh": refresh,
            },
        )
        """
        {
            "path": "/t",
            "password": "",
            "page": 1,
            "per_page": 0,
            "refresh": false
        }
        ======================================
        {
            "code": 200,
            "message": "success",
            "data": {
                "name": "Alist V3.md",
                "size": 2618,
                "is_dir": false,
                "modified": "2024-05-17T16:05:36.4651534+08:00",
                "created": "2024-05-17T16:05:29.2001008+08:00",
                "sign": "",
                "thumb": "",
                "type": 4,
                "hashinfo": "null",
                "hash_info": null,
                "raw_url": "http://127.0.0.1:5244/p/local/Alist%20V3.md",
                "readme": "",
                "header": "",
                "provider": "Local",
                "related": null
            }
        }
        """
        if resp is None:
            logger.warn(f"【OpenList】请求获取文件 {path} 失败，无法连接alist服务")
            return None
        if resp.status_code != 200:
            logger.warn(
                f"【OpenList】请求获取文件 {path} 失败，状态码：{resp.status_code}"
            )
            return None

        result = resp.json()
        if result["code"] != 200:
            logger.debug(
                f"【OpenList】获取文件 {path} 失败，错误信息：{result['message']}"
            )
            return None

        return schemas.FileItem(
            storage=self.schema.value,
            type="dir" if result["data"]["is_dir"] else "file",
            path=path.as_posix() + ("/" if result["data"]["is_dir"] else ""),
            name=result["data"]["name"],
            basename=Path(result["data"]["name"]).stem,
            extension=Path(result["data"]["name"]).suffix[1:],
            size=result["data"]["size"],
            modify_time=self.__parse_timestamp(result["data"]["modified"]),
            thumbnail=result["data"]["thumb"],
        )

    def get_parent(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
        """
        获取父目录

        :param fileitem: 文件项
        :return: 父目录项
        """
        return self.get_folder(Path(fileitem.path).parent)

    def delete(self, fileitem: schemas.FileItem) -> bool:
        """
        删除文件或目录

        :param fileitem: 文件项
        :return: 是否删除成功
        """
        path = Path(fileitem.path)
        name = fileitem.name or path.name
        if not name:
            logger.warn(f"【OpenList】删除路径 {fileitem.path} 无效")
            return False

        resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
            self.__get_api_url("/api/fs/remove"),
            json={
                "dir": path.parent.as_posix(),
                "names": [name],
            },
        )
        if resp is None:
            logger.warn(
                f"【OpenList】请求删除文件 {fileitem.path} 失败，无法连接alist服务"
            )
            return False
        if resp.status_code != 200:
            logger.warn(
                f"【OpenList】请求删除文件 {fileitem.path} 失败，状态码：{resp.status_code}"
            )
            return False
        result = resp.json()
        if result["code"] != 200:
            logger.warn(
                f"【OpenList】删除文件 {fileitem.path} 失败，错误信息：{result['message']}"
            )
            return False
        return True

    def rename(self, fileitem: schemas.FileItem, name: str) -> bool:
        """
        重命名文件

        :param fileitem: 文件项
        :param name: 新文件名
        :return: 是否重命名成功
        """
        resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
            self.__get_api_url("/api/fs/rename"),
            json={
                "name": name,
                "path": fileitem.path,
            },
        )
        """
        {
            "name": "test3",
            "path": "/阿里云盘/test2"
        }
        ======================================
        {
            "code": 200,
            "message": "success",
            "data": null
        }
        """
        if not resp:
            logger.warn(
                f"【OpenList】请求重命名文件 {fileitem.path} 失败，无法连接alist服务"
            )
            return False
        if resp.status_code != 200:
            logger.warn(
                f"【OpenList】请求重命名文件 {fileitem.path} 失败，状态码：{resp.status_code}"
            )
            return False

        result = resp.json()
        if result["code"] != 200:
            logger.warn(
                f"【OpenList】重命名文件 {fileitem.path} 失败，错误信息：{result['message']}"
            )
            return False

        return True

    def download(
        self,
        fileitem: schemas.FileItem,
        path: Path = None,
        password: Optional[str] = "",
    ) -> Optional[Path]:
        """
        下载文件，保存到本地，返回本地临时文件地址
        :param fileitem: 文件项
        :param path: 文件保存路径
        :param password: 文件密码
        :return: 本地临时文件地址
        """
        resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
            self.__get_api_url("/api/fs/get"),
            json={
                "path": fileitem.path,
                "password": password,
                "page": 1,
                "per_page": 0,
                "refresh": False,
            },
        )
        """
        {
            "code": 200,
            "message": "success",
            "data": {
                "name": "[ANi]輝夜姬想讓人告白～天才們的戀愛頭腦戰～[01][1080P][Baha][WEB-DL].mp4",
                "size": 924933111,
                "is_dir": false,
                "modified": "1970-01-01T00:00:00Z",
                "created": "1970-01-01T00:00:00Z",
                "sign": "1v0xkMQz_uG8fkEOQ7-l58OnbB-g4GkdBlUBcrsApCQ=:0",
                "thumb": "",
                "type": 2,
                "hashinfo": "null",
                "hash_info": null,
                "raw_url": "xxxxxx",
                "readme": "",
                "header": "",
                "provider": "UrlTree",
                "related": null
            }
        }
        """
        if not resp:
            logger.warn(f"【OpenList】请求获取文件 {path} 失败，无法连接alist服务")
            return None
        if resp.status_code != 200:
            logger.warn(
                f"【OpenList】请求获取文件 {path} 失败，状态码：{resp.status_code}"
            )
            return None

        result = resp.json()
        if result["code"] != 200:
            logger.warn(
                f"【OpenList】获取文件 {path} 失败，错误信息：{result['message']}"
            )
            return None

        if result["data"]["raw_url"]:
            download_url = result["data"]["raw_url"]
        else:
            download_url = UrlUtils.adapt_request_url(
                self.__get_base_url, f"/d{fileitem.path}"
            )
            if result["data"]["sign"]:
                download_url = download_url + "?sign=" + result["data"]["sign"]

        if not path:
            local_path = settings.TEMP_PATH / fileitem.name
        else:
            local_path = path / fileitem.name

        request_utils = RequestUtils(headers=self.__get_header_with_token())
        try:
            with request_utils.get_stream(download_url, raise_exception=True) as r:
                r.raise_for_status()
                with open(local_path, "wb") as f:
                    for chunk in r.iter_content(chunk_size=8192):
                        if global_vars.is_transfer_stopped(fileitem.path):
                            logger.info(f"【OpenList】{fileitem.path} 下载已取消！")
                            return None
                        f.write(chunk)
        except Exception as e:
            logger.error(f"【OpenList】下载文件 {fileitem.path} 失败：{e}")
            if local_path.exists():
                return local_path

        return local_path

    def upload(
        self,
        fileitem: schemas.FileItem,
        path: Path,
        new_name: Optional[str] = None,
        task: bool = False,
    ) -> Optional[schemas.FileItem]:
        """
        上传文件（带进度）
        :param fileitem: 上传目录项
        :param path: 本地文件路径
        :param new_name: 上传后文件名
        :param task: 是否为任务，默认为False避免未完成上传时对文件进行操作
        :return: 上传后的文件项
        """
        try:
            # 获取文件大小
            target_name = new_name or path.name
            target_path = Path(fileitem.path) / target_name
            stat = path.stat()

            # 初始化进度回调
            progress_callback = transfer_process(path.as_posix())

            # 准备上传请求
            encoded_path = UrlUtils.quote(target_path.as_posix())
            headers = self.__get_header_with_token()
            headers.setdefault("Content-Type", "application/octet-stream")
            headers.setdefault("As-Task", str(task).lower())
            headers.setdefault("File-Path", encoded_path)
            headers.setdefault("Content-Length", str(stat.st_size))
            headers.setdefault("Last-Modified", str(int(stat.st_mtime * 1000)))
            headers.update(self.__get_upload_hash_headers(path))

            # 创建自定义的文件流，支持进度回调
            class ProgressFileReader:
                def __init__(self, file_path: Path, callback):
                    self.file = open(file_path, "rb")
                    self.callback = callback
                    self.uploaded_size = 0
                    self.file_size = file_path.stat().st_size

                def __len__(self) -> int:
                    return self.file_size

                def read(self, size=-1):
                    if global_vars.is_transfer_stopped(path.as_posix()):
                        logger.info(f"【OpenList】{path} 上传已取消！")
                        raise OperationInterrupted(f"Upload cancelled: {path}")
                    chunk = self.file.read(size)
                    if chunk:
                        self.uploaded_size += len(chunk)
                        if self.callback:
                            percent = (self.uploaded_size * 100) / self.file_size
                            self.callback(percent)
                    return chunk

                def close(self):
                    self.file.close()

            # 使用自定义文件流上传
            progress_reader = ProgressFileReader(path, progress_callback)
            try:
                resp = RequestUtils(headers=headers, timeout=6000).put_res(
                    self.__get_api_url("/api/fs/put"),
                    data=progress_reader,
                )
            except OperationInterrupted:
                return None
            finally:
                progress_reader.close()

            if resp is None:
                logger.warn(f"【OpenList】请求上传文件 {path} 失败")
                return None
            if resp.status_code != 200:
                logger.warn(
                    f"【OpenList】请求上传文件 {path} 失败，状态码：{resp.status_code}"
                )
                return None

            # 完成上传
            progress_callback(100)

            # 获取上传后的文件项
            new_item = self._delay_get_item(target_path, refresh=True)
            if new_item and new_name and new_name != path.name:
                if self.rename(new_item, new_name):
                    return self._delay_get_item(
                        Path(new_item.path).with_name(new_name), refresh=True
                    )

            return new_item

        except Exception as e:
            logger.error(f"【OpenList】上传文件 {path} 失败：{e}")
            return None

    @staticmethod
    def __get_upload_hash_headers(path: Path) -> dict:
        """
        计算 OpenList 秒传所需的文件哈希请求头。
        """
        md5_hash = hashlib.md5()
        sha1_hash = hashlib.sha1()
        sha256_hash = hashlib.sha256()
        with open(path, "rb") as file_handler:
            while True:
                chunk = file_handler.read(1024 * 1024)
                if not chunk:
                    break
                md5_hash.update(chunk)
                sha1_hash.update(chunk)
                sha256_hash.update(chunk)
        return {
            "X-File-Md5": md5_hash.hexdigest(),
            "X-File-Sha1": sha1_hash.hexdigest(),
            "X-File-Sha256": sha256_hash.hexdigest(),
        }

    def detail(self, fileitem: schemas.FileItem) -> Optional[schemas.FileItem]:
        """
        获取文件详情
        """
        return self.get_item(Path(fileitem.path))

    def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
        """
        复制文件
        :param fileitem: 文件项
        :param path: 目标目录
        :param new_name: 新文件名
        :return: 是否复制成功
        """
        resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
            self.__get_api_url("/api/fs/copy"),
            json={
                "src_dir": Path(fileitem.path).parent.as_posix(),
                "dst_dir": path.as_posix(),
                "names": [fileitem.name],
            },
        )
        """
        {
            "src_dir": "string",
            "dst_dir": "string",
            "names": [
                "string"
            ]
        }
        ======================================
        {
            "code": 200,
            "message": "success",
            "data": null
        }
        """
        if resp is None:
            logger.warn(
                f"【OpenList】请求复制文件 {fileitem.path} 失败，无法连接alist服务"
            )
            return False
        if resp.status_code != 200:
            logger.warn(
                f"【OpenList】请求复制文件 {fileitem.path} 失败，状态码：{resp.status_code}"
            )
            return False

        result = resp.json()
        if result["code"] != 200:
            logger.warn(
                f"【OpenList】复制文件 {fileitem.path} 失败，错误信息：{result['message']}"
            )
            return False
        # 重命名
        if fileitem.name != new_name:
            new_item = self._delay_get_item(path / fileitem.name, refresh=True)
            if new_item:
                self.rename(new_item, new_name)
        return True

    def copy_item(
            self, fileitem: schemas.FileItem, path: Path, new_name: str
    ) -> Optional[schemas.FileItem]:
        """
        复制文件并返回目标文件项，兼容 OpenList 成功响应不携带目标对象的格式。
        """
        if not self.copy(fileitem=fileitem, path=path, new_name=new_name):
            return None
        target_path = path / new_name
        target_item = self._delay_get_item(target_path, refresh=True)
        if target_item:
            return target_item
        if fileitem.name == new_name:
            return self.__build_transfer_item(fileitem, target_path)

        copied_item = self._delay_get_item(path / fileitem.name, refresh=True)
        if copied_item and self.rename(copied_item, new_name):
            return self._delay_get_item(
                target_path, refresh=True
            ) or self.__build_transfer_item(fileitem, target_path)
        return None

    def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool:
        """
        移动文件
        :param fileitem: 文件项
        :param path: 目标目录
        :param new_name: 新文件名
        :return: 是否移动成功
        """
        # 先重命名
        if fileitem.name != new_name:
            self.rename(fileitem, new_name)
        resp = RequestUtils(headers=self.__get_header_with_token()).post_res(
            self.__get_api_url("/api/fs/move"),
            json={
                "src_dir": Path(fileitem.path).parent.as_posix(),
                "dst_dir": path.as_posix(),
                "names": [new_name],
            },
        )
        """
        {
            "src_dir": "string",
            "dst_dir": "string",
            "names": [
                "string"
            ]
        }
        ======================================
        {
            "code": 200,
            "message": "success",
            "data": null
        }
        """
        if resp is None:
            logger.warn(
                f"【OpenList】请求移动文件 {fileitem.path} 失败，无法连接alist服务"
            )
            return False
        if resp.status_code != 200:
            logger.warn(
                f"【OpenList】请求移动文件 {fileitem.path} 失败，状态码：{resp.status_code}"
            )
            return False

        result = resp.json()
        if result["code"] != 200:
            logger.warn(
                f"【OpenList】移动文件 {fileitem.path} 失败，错误信息：{result['message']}"
            )
            return False
        return True

    def move_item(
            self, fileitem: schemas.FileItem, path: Path, new_name: str
    ) -> Optional[schemas.FileItem]:
        """
        移动文件并返回目标文件项，兼容 OpenList 成功响应不携带目标对象的格式。
        """
        if not self.move(fileitem=fileitem, path=path, new_name=new_name):
            return None
        target_path = path / new_name
        return self._delay_get_item(target_path, refresh=True) or self.__build_transfer_item(
            fileitem, target_path
        )

    def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
        """
        硬链接文件
        """
        pass

    def softlink(self, fileitem: schemas.FileItem, target_file: Path) -> bool:
        """
        软链接文件
        """
        pass

    def usage(self) -> Optional[schemas.StorageUsage]:
        """
        存储使用情况
        """
        pass

    @staticmethod
    def __parse_timestamp(time_str: str) -> float:
        """
        直接使用 ISO 8601 格式解析时间
        """
        return datetime.fromisoformat(time_str).timestamp()
