from typing import Annotated

from arcade.sdk import ToolContext, tool
from arcade.sdk.auth import Atlassian
from arcade.sdk.errors import ToolExecutionError

from arcade_confluence.client import ConfluenceClientV2
from arcade_confluence.enums import BodyFormat, PageSortOrder, PageUpdateMode
from arcade_confluence.utils import remove_none_values, validate_ids


@tool(
    requires_auth=Atlassian(
        scopes=["read:page:confluence", "write:page:confluence"],
    )
)
async def create_page(
    context: ToolContext,
    space_identifier: Annotated[str, "The ID or title of the space to create the page in"],
    title: Annotated[str, "The title of the page"],
    content: Annotated[str, "The content of the page. Only plain text is supported"],
    parent_id: Annotated[
        str | None,
        "The ID of the parent. If not provided, the page will be created at the root of the space.",
    ] = None,
    is_private: Annotated[
        bool,
        "If true, then only the user who creates this page will be able to see it. "
        "Defaults to False",
    ] = False,
    is_draft: Annotated[
        bool,
        "If true, then the page will be created as a draft. Defaults to False",
    ] = False,
) -> Annotated[dict, "The page"]:
    """Create a new page at the root of the given space."""
    client = ConfluenceClientV2(context.get_auth_token_or_empty())
    space_id = await client.get_space_id(space_identifier)

    parent_id = parent_id or (await client.get_space_homepage(space_id)).get("id")
    params = remove_none_values({
        "root-level": False,
        "private": is_private,
    })

    body = remove_none_values({
        "spaceId": space_id,
        "status": "draft" if is_draft else None,
        "parentId": parent_id,
        "title": title,
        "body": {
            "storage": {
                "value": content,
                "representation": BodyFormat.STORAGE.to_api_value(),
            }
        },
    })
    page = await client.post("pages", params=params, json=body)
    return client.transform_page_response(page)


@tool(
    requires_auth=Atlassian(
        scopes=["read:page:confluence", "write:page:confluence"],
    )
)
async def update_page_content(
    context: ToolContext,
    page_identifier: Annotated[
        str, "The ID or title of the page to update. Numerical titles are NOT supported."
    ],
    content: Annotated[str, "The content of the page. Only plain text is supported"],
    update_mode: Annotated[
        PageUpdateMode,
        "The mode of update. Defaults to 'append'.",
    ] = PageUpdateMode.APPEND,
) -> Annotated[dict, "The page"]:
    """Update a page's content."""
    # Get the page to update
    client = ConfluenceClientV2(context.get_auth_token_or_empty())
    page_id = await client.get_page_id(page_identifier)

    page = await get_page(context, page_id)
    if not page.get("page"):
        raise ToolExecutionError(message=f"No page found with identifier: '{page_identifier}'")
    status = page.get("page", {}).get("status", "current")
    title = page.get("page", {}).get("title", "Untitled page")
    body = page.get("page", {}).get("body", {})
    old_content = body.get(BodyFormat.STORAGE, {}).get("value", "")
    old_version_number = page.get("page", {}).get("version", {}).get("number", 0)

    # Update the page content
    payload = client.prepare_update_page_content_payload(
        content=content,
        update_mode=update_mode,
        old_content=old_content,
        page_id=page_id,
        status=status,
        title=title,
        body_representation=BodyFormat.STORAGE,
        old_version_number=old_version_number,
    )
    updated_page = await client.put(f"pages/{page_id}", json=payload)

    return client.transform_page_response(updated_page)


@tool(
    requires_auth=Atlassian(
        scopes=["read:page:confluence", "write:page:confluence"],
    )
)
async def rename_page(
    context: ToolContext,
    page_identifier: Annotated[
        str, "The ID or title of the page to rename. Numerical titles are NOT supported."
    ],
    title: Annotated[str, "The title of the page"],
) -> Annotated[dict, "The page"]:
    """Rename a page by changing its title."""
    # Get the page to rename
    client = ConfluenceClientV2(context.get_auth_token_or_empty())
    page_id = await client.get_page_id(page_identifier)

    page = await get_page(context, page_id)
    if not page.get("page"):
        raise ToolExecutionError(message=f"No page found with identifier: '{page_identifier}'")
    status = page.get("page", {}).get("status", "current")
    content = page.get("page", {}).get("body", {}).get(BodyFormat.STORAGE, {}).get("value", "")
    old_version_number = page.get("page", {}).get("version", {}).get("number", 0)

    # Rename the page
    payload = client.prepare_update_page_payload(
        page_id=page_id,
        status=status,
        title=title,
        body_representation=BodyFormat.STORAGE,
        body_value=content,
        version_number=old_version_number + 1,
        version_message="Rename the page",
    )
    updated_page = await client.put(f"pages/{page_id}", json=payload)

    return client.transform_page_response(updated_page)


@tool(
    requires_auth=Atlassian(
        scopes=["read:page:confluence"],
    )
)
async def get_page(
    context: ToolContext,
    page_identifier: Annotated[
        str, "Can be a page's ID or title. Numerical titles are NOT supported."
    ],
) -> Annotated[dict, "The page"]:
    """Retrieve a SINGLE page's content by its ID or title.

    If a title is provided, then the first page with an exact matching title will be returned.

    IMPORTANT: For retrieving MULTIPLE pages, use `get_pages_by_id` instead
    for a massive performance and efficiency boost. If you call this function multiple times
    instead of using `get_pages_by_id`, then the universe will explode.
    """
    client = ConfluenceClientV2(context.get_auth_token_or_empty())
    if page_identifier.isdigit():
        return await client.get_page_by_id(page_identifier, BodyFormat.STORAGE)
    else:
        return await client.get_page_by_title(page_identifier, BodyFormat.STORAGE)


@tool(
    requires_auth=Atlassian(
        scopes=["read:page:confluence"],
    )
)
async def get_pages_by_id(
    context: ToolContext,
    page_ids: Annotated[
        list[str],
        "The IDs of the pages to get. IDs are numeric. Titles of pages are NOT supported. "
        "Maximum of 250 page ids supported.",
    ],
) -> Annotated[dict, "The pages"]:
    """Get the content of MULTIPLE pages by their ID in a single efficient request.

    IMPORTANT: Always use this function when you need to retrieve content from more than one page,
    rather than making multiple separate calls to get_page, because this function is significantly
    more efficient than calling get_page multiple times.
    """
    validate_ids(page_ids, max_length=250)
    client = ConfluenceClientV2(context.get_auth_token_or_empty())
    pages = await client.get(
        "pages", params={"id": page_ids, "body-format": BodyFormat.STORAGE.to_api_value()}
    )
    return client.transform_get_multiple_pages_response(pages)


@tool(
    requires_auth=Atlassian(
        scopes=["read:page:confluence"],
    )
)
async def list_pages(
    context: ToolContext,
    space_ids: Annotated[
        list[str] | None,
        "Restrict the response to only include pages in these spaces. "
        "Only space IDs are supported. Titles of spaces are NOT supported. "
        "If not provided, then no restriction is applied. "
        "Maximum of 100 space ids supported.",
    ] = None,
    sort_by: Annotated[
        PageSortOrder,
        "The order of the pages to sort by. Defaults to created-date-newest-to-oldest",
    ] = PageSortOrder.CREATED_DATE_DESCENDING,
    limit: Annotated[int, "The maximum number of pages to return. Defaults to 25. Max is 250"] = 25,
    pagination_token: Annotated[
        str | None,
        "The pagination token to use for the next page of results",
    ] = None,
) -> Annotated[dict, "The pages"]:
    """Get the content of multiple pages by their ID"""
    validate_ids(space_ids, max_length=100)
    limit = max(1, min(limit, 250))
    client = ConfluenceClientV2(context.get_auth_token_or_empty())
    params = remove_none_values({
        "space-id": space_ids,
        "sort": sort_by.to_api_value(),
        "body-format": BodyFormat.STORAGE.to_api_value(),
        "limit": limit,
        "cursor": pagination_token,
    })
    pages = await client.get("pages", params=params)
    return client.transform_list_pages_response(pages)
