"""
Moebooru Image Board API implementation.
"""

import os
from typing import AsyncIterable
from niquests.typing import HttpAuthenticationType, AsyncHttpAuthenticationType

import pandas as pd
from asyncstdlib import enumerate as aenumerate
from niquests.exceptions import RequestException
from lxml import etree

from .booru import Booru, BooruComponent
from .utils import logger

__all__ = [
    # base classes
    "Moebooru",
    "MoebooruComponent",
    # client classes
    "YandereClient",
    # component classes
    "YanderePosts",
    "YandereTags",
    "YandereArtists",
    "YandereComments",
    "YandereWiki",
    "YandereNotes",
    "YandereUsers",
    "YandereForum",
    "YanderePools",
    "YandereFavorites",
]


class Moebooru(Booru):
    """
    Moebooru Image Board API
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)


class MoebooruComponent(BooruComponent):
    """
    Moebooru Image Board Component
    """

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


class YandereClient(Moebooru):
    """
    Yandere API reference document: https://yande.re/help/api
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # 设置发送相对 URL 请求时使用的基础 URL
        self.base_url = "https://yande.re"

        # 初始化各组件
        self.posts = YanderePosts(self)
        # self.tags = YandereTags(self)
        # self.artists = YandereArtists(self)
        # self.comments = YandereComments(self)
        # self.wiki = YandereWiki(self)
        # self.notes = YandereNotes(self)
        # self.users = YandereUsers(self)
        # self.forum = YandereForum(self)
        self.pools = YanderePools(self)
        # self.favorites = YandereFavorites(self)

        # 登录后修改（默认无账号）

    def login(self, auth: HttpAuthenticationType | AsyncHttpAuthenticationType | None):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YanderePosts(MoebooruComponent):
    """
    Posts: https://yande.re/help/api#posts
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    async def list_gt_page(
        self,
        limit: int = 40,
        tags: str = "",
    ) -> int:
        """
        使用定位 html 分页器的方式，获取指定标签帖子列表的最大页码

        Note:
            对于 post 页面，返回的 html 分页器中的最大页码由于 yande.re 网站中的某些 Hidden Posts 策略（rating:e, blacklists .etc），实际的最大页码会大于等于该页码
            yande.re post 页面展示策略受 limit 参数影响，会自动根据 limit 参数与实际帖子数量调整 html 分页器中的最大页码

        Args:
            limit (int, optional): 您想检索多少篇帖子。每次请求的帖子数量有一个硬性限制，最多 1000 篇. Defaults to 40.
            tags (str, optional): 要搜索的标签。任何在网站上有效的标签组合在这里都有效。这包括所有元标签。要组合的不同标签使用空格连接，同一标签中的空格使用 _ 替换. Defaults to ''. 表示搜索全站

        Returns:
            int: html 分页器中的最大页码，实际的最大页码会大于等于该页码
        """
        url = "/post"
        params = {
            "limit": limit,  # 您想检索多少篇帖子。每次请求的帖子数量有一个硬性限制，最多 1000 篇
            "page": 1,  # 查询页码
            "tags": tags,  # 要搜索的标签。任何在网站上有效的标签组合在这里都有效。这包括所有元标签。要组合的不同标签使用空格连接，同一标签中的空格使用 _ 替换
        }

        try:
            response = await self.client.get(url, params=params)
            # 解析 html 分页器中的最大页码
            tree = etree.HTML(response.text)
            # 形如 ['2', '3', '4', '5', '1067', '1068', 'Next →'] 的样式。列表中的最后一个永远为 'Next →'；由于请求的 url 中的 page 参数固定为 1，当前页码信息 1 使用 em 标签而非 a 标签，故列表若存在，则永远以 2 开头
            pagination = tree.xpath('//div[@class="pagination"]/a[@aria-label]/text()')
            if pagination:  # 存在分页器，说明该页面至少有两页
                return int(pagination[-2])
            else:  # 不存在分页器，说明该页面只有一页
                return 1
        except RequestException as exc:
            logger.error(f"{exc.__class__.__name__} for {exc.request.url} - {exc}")

    async def list(
        self,
        limit: int = 40,
        start_page: int = 1,
        end_page: int = 1,
        all_page: bool = False,
        tags: str = "",
    ) -> AsyncIterable[list[dict] | None]:
        """
        List

        The base URL is /post.xml.

        - limit: How many posts you want to retrieve. There is a hard limit of 100 posts per request.
        - page: The page number.
        - tags: The tags to search for. Any tag combination that works on the web site will work here. This includes all the meta-tags.

        Note:
            修订 API 参考文件: https://yande.re/help/api: limit: How many posts you want to retrieve. There is a hard limit of 100 posts per request. 将其更改为：limit: How many posts you want to retrieve. There is a hard limit of 1000 posts per request.

        Return json format:
        ```
        [
            {
                "id": 1223391,
                "tags": "akiyama_mio bleed_through chibi christmas dress fixme hirasawa_yui horiguchi_yukiko k-on! kotobuki_tsumugi nakano_azusa pantyhose tainaka_ritsu",
                "created_at": 1742150809,
                "updated_at": 1742156680,
                "creator_id": 537635,
                "approver_id": null,
                "author": "Reverseshin",
                "change": 6324472,
                "source": "Animedia 2010-12",
                "score": 3,
                "md5": "3a453ffae99c4de46e4fb5bf82236842",
                "file_size": 26463488,
                "file_ext": "png",
                "file_url": "https://files.yande.re/image/3a453ffae99c4de46e4fb5bf82236842/yande.re%201223391%20akiyama_mio%20bleed_through%20chibi%20christmas%20dress%20fixme%20hirasawa_yui%20horiguchi_yukiko%20k-on%21%20kotobuki_tsumugi%20nakano_azusa%20pantyhose%20tainaka_ritsu.png",
                "is_shown_in_index": false,
                "preview_url": "https://assets.yande.re/data/preview/3a/45/3a453ffae99c4de46e4fb5bf82236842.jpg",
                "preview_width": 150,
                "preview_height": 93,
                "actual_preview_width": 300,
                "actual_preview_height": 186,
                "sample_url": "https://files.yande.re/sample/3a453ffae99c4de46e4fb5bf82236842/yande.re%201223391%20sample%20akiyama_mio%20bleed_through%20chibi%20christmas%20dress%20fixme%20hirasawa_yui%20horiguchi_yukiko%20k-on%21%20kotobuki_tsumugi%20nakano_azusa%20pantyhose%20tainaka_ritsu.jpg",
                "sample_width": 1500,
                "sample_height": 931,
                "sample_file_size": 481240,
                "jpeg_url": "https://files.yande.re/jpeg/3a453ffae99c4de46e4fb5bf82236842/yande.re%201223391%20akiyama_mio%20bleed_through%20chibi%20christmas%20dress%20fixme%20hirasawa_yui%20horiguchi_yukiko%20k-on%21%20kotobuki_tsumugi%20nakano_azusa%20pantyhose%20tainaka_ritsu.jpg",
                "jpeg_width": 3500,
                "jpeg_height": 2172,
                "jpeg_file_size": 2088523,
                "rating": "s",
                "is_rating_locked": false,
                "has_children": false,
                "parent_id": 162305,
                "status": "active",
                "is_pending": false,
                "width": 5492,
                "height": 3408,
                "is_held": false,
                "frames_pending_string": "",
                "frames_pending": [],
                "frames_string": "",
                "frames": [],
                "is_note_locked": false,
                "last_noted_at": 0,
                "last_commented_at": 0
            },
            ...
        ]
        ```

        获取在起始页码与结束页码范围内，指定标签的帖子列表；若 all_page 为 True，则获取当前查询标签下所有页码的帖子列表

        Args:
            limit (int, optional): 您想检索多少篇帖子。每次请求的帖子数量有一个硬性限制，最多 1000 篇. Defaults to 40.
            start_page (int, optional): 查询起始页码. Defaults to 1.
            end_page (int, optional): 查询结束页码. Defaults to 1.
            all_page (bool, optional): 是否获取当前查询标签下所有页码的帖子列表，若为 True，则忽略 start_page 与 end_page 参数. Defaults to False.
            tags (str, optional): 要搜索的标签。任何在网站上有效的标签组合在这里都有效。这包括所有元标签。要组合的不同标签使用空格连接，同一标签中的空格使用 _ 替换. Defaults to ''. 表示搜索全站

        Yields:
            list[dict] | None. 若获取成功，则返回对应的帖子内容列表；若获取失败，则返回 None
        """
        if limit > 1000:  # 事实上，超过该值时，返回的结果会被截断到该值
            limit = 1000
            logger.warning(
                f"Limit is set to {limit}, Because it exceeds the maximum allowed value of 1000."
            )

        url = "/post.json"
        params = {
            "limit": limit,  # 您想检索多少篇帖子。每次请求的帖子数量有一个硬性限制，最多 1000 篇
            "page": 1,  # 查询页码
            "tags": tags,  # 要搜索的标签。任何在网站上有效的标签组合在这里都有效。这包括所有元标签。要组合的不同标签使用空格连接，同一标签中的空格使用 _ 替换
        }

        # 获取当前查询标签下所有页码的帖子列表
        if all_page:
            gt_page = await self.list_gt_page(  # 获取 html 分页器中的最大页码
                limit=limit,
                tags=tags,
            )
            logger.info(
                f"Maximum page number is greater than or equal to {gt_page} for {limit = }, {tags = }"
            )

            async for res in self.client.concurrent_fetch_page(
                url,
                params=params,
                start_page=1,
                end_page=gt_page,
                page_key="page",
            ):
                yield res

            #!仅适用于 posts 页面
            #!为防止遗漏帖子列表，回退至非并发模式获取 html 分页器中的最大页码之后的帖子列表
            logger.info(
                f"Fetching posts after {gt_page} page for {limit = }, {tags = }"
            )

            # 当前查询页码
            page = gt_page + 1
            # 直到获取到空数据为止
            while True:
                params.update({"page": page})
                content: list[dict] = await self.client.fetch_page(
                    url,
                    params=params,
                )
                if content:
                    yield content
                    page += 1
                else:
                    break

        # 获取在起始页码与结束页码范围内，指定标签的帖子列表
        else:
            async for res in self.client.concurrent_fetch_page(
                url,
                params=params,
                start_page=start_page,
                end_page=end_page,
                page_key="page",
            ):
                yield res

    def create(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def update(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def destroy(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def revert_tags(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def vote(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    async def download(
        self,
        limit: int = 40,
        start_page: int = 1,
        end_page: int = 1,
        all_page: bool = False,
        tags: str = "",
        save_raws: bool = False,
        save_tags: bool = False,
        overwrite: bool = False,
    ) -> None:
        """
        下载在起始页码与结束页码范围内，指定标签的帖子列表中的帖子；若 all_page 为 True，则下载当前查询标签下所有页码的帖子列表中的帖子

        Args:
            limit (int, optional): 您想检索多少篇帖子。每次请求的帖子数量有一个硬性限制，最多 1000 篇. Defaults to 40.
            start_page (int, optional): 查询起始页码. Defaults to 1.
            end_page (int, optional): 查询结束页码. Defaults to 1.
            all_page (bool, optional): 是否获取当前查询标签下所有页码的帖子列表，若为 True，则忽略 start_page 与 end_page 参数. Defaults to False.
            tags (str, optional): 要搜索的标签。任何在网站上有效的标签组合在这里都有效。这包括所有元标签。要组合的不同标签使用空格连接，同一标签中的空格使用 _ 替换. Defaults to ''. 表示搜索全站
            save_raws (bool, optional): 是否保存帖子 api 响应的元数据（json 格式）. Defaults to False.
            save_tags (bool, optional): 是否保存帖子标签. Defaults to False.
            overwrite (bool, optional): 是否覆盖已有同名文件. Defaults to False.
        """
        # 获取当前查询标签下所有页码的帖子列表中的帖子
        async for i, posts in aenumerate(
            self.list(
                limit=limit,
                start_page=start_page,
                end_page=end_page,
                all_page=all_page,
                tags=tags,
            )
        ):
            posts = pd.DataFrame(posts)

            if posts.empty:
                logger.info(f"All of the posts {i + 1} are empty.")
                continue

            # 下载帖子
            urls = posts["file_url"]  # 帖子 URLs
            posts_directory = os.path.join(
                self.directory, f"{tags if tags != '' else 'all'}"
            )  # 帖子文件目录
            images_directory = os.path.join(posts_directory, "images")  # 图像文件目录

            success_count, failure_count = 0, 0
            async for index, res in aenumerate(
                self.client.concurrent_download_file(
                    urls,
                    images_directory,
                )
            ):
                if res is None:
                    failure_count += 1
                    continue
                else:
                    success_count += 1

                url, filepath = res

                # 保存帖子 api 响应的元数据（json 格式）
                if save_raws:
                    # 保存元数据
                    raws = posts.loc[[index]]  # 筛选后的元数据
                    raws_directory = os.path.join(
                        posts_directory, "raws"
                    )  # 元数据文件目录
                    raws_filename = (
                        os.path.splitext(os.path.basename(filepath))[0] + ".json"
                    )  # 元数据文件名
                    await self.client.save_raws(
                        raws,
                        directory=raws_directory,
                        filename=raws_filename,
                        overwrite=overwrite,
                    )

                # 保存标签
                if save_tags:
                    # 帖子标签
                    tags = posts.at[index, "tags"]  # 筛选后的 tags
                    tags_directory = os.path.join(
                        posts_directory, "tags"
                    )  # 标签文件目录
                    tags_filename = (
                        os.path.splitext(os.path.basename(filepath))[0] + ".txt"
                    )  # 标签文件名
                    await self.client.save_tags(
                        tags,
                        directory=tags_directory,
                        filename=tags_filename,
                        overwrite=overwrite,
                    )

            logger.info(
                f"Downloaded {success_count} successful, {failure_count} failed for posts: {posts['id'].tolist()}"
            )


class YandereTags(MoebooruComponent):
    """
    Tags: https://yande.re/help/api#tags
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def list(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def update(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def related(self):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YandereArtists(MoebooruComponent):
    """
    Artists: https://yande.re/help/api#artists
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def list(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def create(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def update(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def destroy(self):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YandereComments(MoebooruComponent):
    """
    Comments: https://yande.re/help/api#comments
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def show(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def create(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def destory(self):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YandereWiki(MoebooruComponent):
    """
    Wiki: https://yande.re/help/api#wiki
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def list(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def create(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def update(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def show(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def destroy(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def lock(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def unlock(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def revert(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def history(self):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YandereNotes(MoebooruComponent):
    """
    Notes: https://yande.re/help/api#notes
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def list(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def search(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def history(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def revert(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def create(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def update(self):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YandereUsers(MoebooruComponent):
    """
    Users: https://yande.re/help/api#users
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def search(self):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YandereForum(MoebooruComponent):
    """
    Forum: https://yande.re/help/api#forum
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def list(self):
        # TODO
        raise NotImplementedError("The method is not implemented")


class YanderePools(MoebooruComponent):
    """
    Pools: https://yande.re/help/api#pools
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    async def list_pools_page(
        self,
        query: str = "",
    ) -> int:
        """
        使用定位 html 分页器的方式，获取指定查询标题图集的最大页码

        Note:
            对于 pool 页面，由于不存在 Hidden Posts 策略（rating:e, blacklists .etc），实际的最大页码会等于该页码
            yande.re/pool 页面受 page 参数影响，但不受 limit 参数影响

        Args:
            query (str, optional): 查询标题. Defaults to ''. 表示搜索全站

        Returns:
            int: html 分页器中的最大页码，实际的最大页码等于该页码
        """
        url = "/pool"
        params = {
            "query": query,  # 查询标题
            "page": 1,  # 查询页码
        }

        try:
            response = await self.client.get(url, params=params)
            # 解析 html 分页器中的最大页码
            tree = etree.HTML(response.text)
            # 形如 ['2', '3', '4', '5', '1067', '1068', 'Next →'] 的样式。列表中的最后一个永远为 'Next →'；由于请求的 url 中的 page 参数固定为 1，当前页码信息 1 使用 em 标签而非 a 标签，故列表若存在，则永远以 2 开头
            pagination = tree.xpath('//div[@class="pagination"]/a[@aria-label]/text()')
            if pagination:  # 存在分页器，说明该页面至少有两页
                return int(pagination[-2])
            else:  # 不存在分页器，说明该页面只有一页
                return 1
        except RequestException as exc:
            logger.error(f"{exc.__class__.__name__} for {exc.request.url} - {exc}")

    async def list_pools(
        self,
        query: str = "",
        start_page: int = 1,
        end_page: int = 1,
        all_page: bool = False,
    ) -> AsyncIterable[list[dict] | None]:
        """
        List Pools

        The base URL is /pool.xml. If you don't specify any parameters you'll get a list of all pools.

        - query: The title.
        - page: The page.

        Return json format:
        ```
        [
            {
                "id": 1746,
                "name": "K-ON!_-_Colorful_Memories",
                "created_at": "2010-07-18T00:21:15.758Z",
                "updated_at": "2012-03-11T19:10:27.707Z",
                "user_id": 3048,
                "is_public": true,
                "post_count": 44,
                "description": "http://kyotoanimation.shop-pro.jp/?pid=20190414"
            },
            ...
        ]
        ```

        获取在起始页码与结束页码范围内，指定标题的图集列表；若 all_page 为 True，则获取当前查询标题下所有页码的图集列表

        Note:
            yande.re/pool 接口受 page 参数影响，但不受 limit 参数影响

        Args:
            query (str, optional): 查询标题. Defaults to ''. 表示搜索全站
            start_page (int, optional): 查询起始页码. Defaults to 1.
            end_page (int, optional): 查询结束页码. Defaults to 1.
            all_page (bool, optional): 是否获取当前查询标题下所有页码的图集列表，若为 True，则忽略 start_page 与 end_page 参数. Defaults to False.

        Yields:
            AsyncIterable[list[dict] | None]: 若获取成功，则返回对应的图集内容列表；若获取失败，则返回 None
        """
        url = "/pool.json"
        params = {
            "query": query,  # 查询标题
            "page": 1,  # 查询页码
        }

        # 获取当前查询标题下所有页码的图集列表
        if all_page:
            max_page = await self.list_pools_page(
                query=query,
            )  # 获取 html 分页器中的最大页码
            logger.info(f"Maximum page number is equal to {max_page} for {query = }")

            async for res in self.client.concurrent_fetch_page(
                url,
                params=params,
                start_page=1,
                end_page=max_page,
                page_key="page",
            ):
                yield res

        # 获取在起始页码与结束页码范围内，指定标题的图集列表
        else:
            async for res in self.client.concurrent_fetch_page(
                url,
                params=params,
                start_page=start_page,
                end_page=end_page,
                page_key="page",
            ):
                yield res

    async def list_posts(
        self,
        id: int,
    ) -> list[dict] | None:
        """
        List Posts

        The base URL is /pool/show.xml. If you don't specify any parameters you'll get a list of all pools.

        Note:
            - 修订 API 参考文件: https://yande.re/help/api: If you don't specify any parameters you'll get a list of all pools. 将其更改为：If you don't specify any parameters you'll get a list of pool which id is 0.
            - id 参数是必须的，否则访问 https://yande.re/pool/show 或 https://yande.re/pool/show.json 是会自动跳转回 https://yande.re/pool/ 页面，并弹出 Can't find pool with id 0 提示
            - page 参数是可选的，pool/show 页面默认不以分页策略展示，所有内容均在一页中展示（忽略 limit 参数），设置为 1 即可。若需要分页策略，需点击页面最下方的 "Index View" 按钮，对于 id 为 1746 的图集，将跳转至 /post?tags=pool%3A1746 访问（即 /post?tags=pool:1746）
            - 图集支持批量下载，需点击页面最下方的 "Download" 按钮，对于 id 为 1746 的图集，将跳转至 /pool/zip/1746 访问，但需要用户登录后才能下载

        - id: The pool id number.
        - page: The page.

        Return json format:
        ```
        {
            "id": 1746,
            "name": "K-ON!_-_Colorful_Memories",
            "created_at": "2010-07-18T00:21:15.758Z",
            "updated_at": "2012-03-11T19:10:27.707Z",
            "user_id": 3048,
            "is_public": true,
            "post_count": 44,
            "description": "http://kyotoanimation.shop-pro.jp/?pid=20190414",
            "posts": [
                {
                    "id": 145519,
                    "tags": "akiyama_mio hirasawa_yui jpeg_artifacts k-on! kotobuki_tsumugi nakano_azusa pantyhose tainaka_ritsu",
                    "created_at": "2010-07-18T00:23:44.162Z",
                    "creator_id": 17990,
                    "author": "Share",
                    "change": 650971,
                    "source": "",
                    "score": 30,
                    "md5": "841ac093c4e6de2dd13ce1fb52703da7",
                    "file_size": 1143459,
                    "file_url": "https://files.yande.re/image/841ac093c4e6de2dd13ce1fb52703da7/yande.re%20145519%20akiyama_mio%20hirasawa_yui%20jpeg_artifacts%20k-on%21%20kotobuki_tsumugi%20nakano_azusa%20pantyhose%20tainaka_ritsu.jpg",
                    "is_shown_in_index": true,
                    "preview_url": "https://assets.yande.re/data/preview/84/1a/841ac093c4e6de2dd13ce1fb52703da7.jpg",
                    "preview_width": 106,
                    "preview_height": 150,
                    "actual_preview_width": 213,
                    "actual_preview_height": 300,
                    "sample_url": "https://files.yande.re/sample/841ac093c4e6de2dd13ce1fb52703da7/yande.re%20145519%20sample%20akiyama_mio%20hirasawa_yui%20jpeg_artifacts%20k-on%21%20kotobuki_tsumugi%20nakano_azusa%20pantyhose%20tainaka_ritsu.jpg",
                    "sample_width": 1065,
                    "sample_height": 1500,
                    "sample_file_size": 271128,
                    "jpeg_url": "https://files.yande.re/image/841ac093c4e6de2dd13ce1fb52703da7/yande.re%20145519%20akiyama_mio%20hirasawa_yui%20jpeg_artifacts%20k-on%21%20kotobuki_tsumugi%20nakano_azusa%20pantyhose%20tainaka_ritsu.jpg",
                    "jpeg_width": 3000,
                    "jpeg_height": 4226,
                    "jpeg_file_size": 0,
                    "rating": "s",
                    "has_children": false,
                    "parent_id": null,
                    "status": "active",
                    "width": 3000,
                    "height": 4226,
                    "is_held": false,
                    "frames_pending_string": "",
                    "frames_pending": [],
                    "frames_string": "",
                    "frames": []
                },
                ...
            ]
        }
        ```

        获取指定 id 的图集列表

        Args:
            id (int): 图集 id

        Returns:
            list[dict] | None: 若获取成功，则返回对应的图集内容列表；若获取失败，则返回 None
        """
        url = "/pool/show.json"
        params = {
            "id": id,  # 图集的 ID 号码
            "page": 1,  # 查询页码
        }

        # 结果列表
        result: list[dict] = await self.client.fetch_page(
            url,
            params=params,
            callback=lambda x: x.get("posts", []),  # 获取帖子列表
        )
        return result

    def update_pool(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def create_pool(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def destroy_pool(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def add_post(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    def remove_post(self):
        # TODO
        raise NotImplementedError("The method is not implemented")

    async def download(
        self,
        query: str = "",
        start_page: int = 1,
        end_page: int = 1,
        all_page: bool = False,
        save_raws: bool = False,
        save_tags: bool = False,
        overwrite: bool = False,
    ) -> None:
        """
        下载在起始页码与结束页码范围内，指定标题的图集列表中的帖子；若 all_page 为 True，则下载当前查询标题下所有页码的图集列表中的帖子

        Args:
            query (str, optional): 查询标题. Defaults to ''. 表示搜索全站
            start_page (int, optional): 查询起始页码. Defaults to 1.
            end_page (int, optional): 查询结束页码. Defaults to 1.
            all_page (bool, optional): 是否下载当前查询标题下所有页码的图集列表中的帖子，若为 True，则忽略 start_page 与 end_page 参数. Defaults to False.
            save_raws (bool, optional): 是否保存帖子 api 响应的元数据（json 格式）. Defaults to False.
            save_tags (bool, optional): 是否保存帖子标签. Defaults to False.
            overwrite (bool, optional): 是否覆盖已有同名文件. Defaults to False.
        """
        # 获取当前查询标题下所有页码的图集列表
        async for i, pools in aenumerate(
            self.list_pools(
                query=query,
                start_page=start_page,
                end_page=end_page,
                all_page=all_page,
            )
        ):
            pools = pd.DataFrame(pools)

            if pools.empty:
                logger.info(f"All of the pools {i + 1} are empty.")
                continue

            # 图集 id
            ids = pools["id"]
            # 图集名称
            names = pools["name"]

            # 遍历图集列表
            for id, name in zip(ids, names):
                # 获取图集 ID 下所有帖子
                posts = await self.list_posts(
                    id=id,
                )
                posts = pd.DataFrame(posts)

                # 下载帖子
                urls = posts["file_url"]  # 帖子 URLs
                posts_directory = os.path.join(
                    self.directory, f"{name}"
                )  # 帖子文件目录
                images_directory = os.path.join(
                    posts_directory, "images"
                )  # 图像文件目录

                success_count, failure_count = 0, 0
                async for index, res in aenumerate(
                    self.client.concurrent_download_file(
                        urls,
                        images_directory,
                    )
                ):
                    if res is None:
                        failure_count += 1
                        continue
                    else:
                        success_count += 1
                    url, filepath = res

                    # 保存帖子 api 响应的元数据（json 格式）
                    if save_raws:
                        # 保存元数据
                        raws = posts.loc[[index]]  # 筛选后的元数据
                        raws_directory = os.path.join(
                            posts_directory, "raws"
                        )  # 元数据文件目录
                        raws_filename = (
                            os.path.splitext(os.path.basename(filepath))[0] + ".json"
                        )  # 元数据文件名
                        await self.client.save_raws(
                            raws,
                            directory=raws_directory,
                            filename=raws_filename,
                            overwrite=overwrite,
                        )

                    # 保存标签
                    if save_tags:
                        # 帖子标签
                        tags = posts.at[index, "tags"]  # 筛选后的 tags
                        tags_directory = os.path.join(
                            posts_directory, "tags"
                        )  # 标签文件目录
                        tags_filename = (
                            os.path.splitext(os.path.basename(filepath))[0] + ".txt"
                        )  # 标签文件名
                        await self.client.save_tags(
                            tags,
                            directory=tags_directory,
                            filename=tags_filename,
                            overwrite=overwrite,
                        )

                logger.info(
                    f"Downloaded {success_count} successful, {failure_count} failed for pool: {name}"
                )


class YandereFavorites(MoebooruComponent):
    """
    Favorites: https://yande.re/help/api#favorites
    """

    def __init__(self, client: YandereClient):
        super().__init__(client)

    def list_users(self):
        # TODO
        raise NotImplementedError("The method is not implemented")
