import asyncio
from typing import Annotated, Any

import httpx
from arcade.sdk import ToolContext, tool
from arcade.sdk.auth import Notion
from arcade.sdk.errors import ToolExecutionError

from arcade_notion_toolkit.block_to_markdown_converter import BlockToMarkdownConverter
from arcade_notion_toolkit.enums import BlockType, ObjectType
from arcade_notion_toolkit.markdown_to_block_converter import convert_markdown_to_blocks
from arcade_notion_toolkit.tools.search import get_object_metadata
from arcade_notion_toolkit.types import DatabaseParent, PageWithPageParentProperties, create_parent
from arcade_notion_toolkit.utils import (
    extract_title,
    get_headers,
    get_next_page,
    get_url,
)


@tool(requires_auth=Notion())
async def get_page_content_by_id(
    context: ToolContext, page_id: Annotated[str, "ID of the page to get content from"]
) -> Annotated[str, "The markdown content of the page"]:
    """Get the content of a Notion page as markdown with the page's ID"""
    headers = get_headers(context)
    params = {"page_size": 100}
    converter = BlockToMarkdownConverter(context)

    async with httpx.AsyncClient() as client:

        async def fetch_blocks(block_id: str) -> list:
            """Fetch all immediate children blocks for a given block ID, handling pagination"""
            all_blocks = []
            url = get_url("retrieve_block_children", block_id=block_id)
            cursor = None

            while True:
                data, has_more, cursor = await get_next_page(client, url, headers, params, cursor)
                all_blocks.extend(data.get("results", []))
                if not has_more:
                    break

            return all_blocks

        async def process_blocks_to_markdown(blocks: list, indent: str = "") -> str:
            """Process a list of blocks into markdown.

            If a block has children, we recurse into the children blocks.
            """
            markdown_pieces = []

            for block in blocks:
                block_markdown = await converter.convert_block(block)
                if block_markdown:
                    # Append each line with indent as a separate piece
                    for line in block_markdown.rstrip("\n").splitlines():
                        markdown_pieces.append(indent + line + "\n")

                # If the block has children and is not a child page, recurse.
                # We don't recurse into child page content, as this would result in fetching
                # the children pages' content, which the Notion UI does not show.
                if (
                    block.get("has_children", False)
                    and block.get("type") != BlockType.CHILD_PAGE.value
                ):
                    # Fetch all child blocks first
                    child_blocks = await fetch_blocks(block["id"])
                    # Then process them all at once
                    child_markdown = await process_blocks_to_markdown(child_blocks, indent + "    ")
                    markdown_pieces.append(child_markdown)

            return "".join(markdown_pieces)

        # Get the title
        page_metadata = await get_object_metadata(context, object_id=page_id)
        markdown_title = f"# {extract_title(page_metadata)}\n"

        # Get all top-level blocks
        top_level_blocks = await fetch_blocks(page_id)

        chunk_size = max(1, len(top_level_blocks) // 5)
        chunks = [
            top_level_blocks[i : i + chunk_size]
            for i in range(0, len(top_level_blocks), chunk_size)
        ]

        # Process all block content into markdown
        results = await asyncio.gather(*[process_blocks_to_markdown(chunk, "") for chunk in chunks])
        markdown_content = "".join(results)

        return markdown_title + markdown_content


@tool(requires_auth=Notion())
async def get_page_content_by_title(
    context: ToolContext, title: Annotated[str, "Title of the page to get content from"]
) -> Annotated[str, "The markdown content of the page"]:
    """Get the content of a Notion page as markdown with the page's title"""
    page_metadata = await get_object_metadata(
        context, object_title=title, object_type=ObjectType.PAGE
    )

    page_content: str = await get_page_content_by_id(context, page_metadata["id"])
    return page_content


@tool(requires_auth=Notion())
async def create_page(
    context: ToolContext,
    parent_title: Annotated[
        str,
        "Title of an existing page/database within which the new page will be created. ",
    ],
    title: Annotated[str, "Title of the new page"],
    content: Annotated[str | None, "The content of the new page"] = None,
) -> Annotated[str, "The ID of the new page"]:
    """Create a new Notion page by the title of the new page's parent."""
    # Notion API does not support creating a page at the root of the workspace... sigh
    parent_metadata = await get_object_metadata(
        context,
        parent_title,
        object_type=ObjectType.PAGE,
    )
    parent_type = parent_metadata["object"] + "_id"
    parent = create_parent({"type": parent_type, parent_type: parent_metadata["id"]})

    properties: dict[str, Any] = {}
    if isinstance(parent, DatabaseParent):
        # TODO: Support creating a page within a database
        raise ToolExecutionError(
            message="Creating a page within a database is not supported.",
            developer_message="Database is not supported as a parent of a new page at this time.",
        )
    else:
        properties = PageWithPageParentProperties(title=title).to_dict()

    children = convert_markdown_to_blocks(content) if content else None

    body = {
        "parent": parent.to_dict(),
        "properties": properties,
        "children": children,
    }

    url = get_url("create_a_page")
    headers = get_headers(context)
    async with httpx.AsyncClient() as client:
        response = await client.post(url, headers=headers, json=body)
        response.raise_for_status()
        return f"Successfully created page with ID: {response.json()['id']}"
