from __future__ import annotations

import re

from lark_oapi.api.docx.v1 import (
    CreateDocumentRequest,
    CreateDocumentRequestBody,
    GetDocumentRequest,
    ListDocumentBlockRequest,
    RawContentDocumentRequest,
)
from lark_oapi.api.wiki.v2 import (
    CreateSpaceNodeRequest,
    GetNodeSpaceRequest,
    ListSpaceNodeRequest,
    GetSpaceRequest,
    ListSpaceRequest,
    MoveDocsToWikiSpaceNodeRequest,
    MoveDocsToWikiSpaceNodeRequestBody,
    Node,
)

from ._client import get_client, make_option
from ._scopes import InsufficientScopeError, MODULE_SCOPES

_WIKI_URL_RE = re.compile(r"/wiki/([A-Za-z0-9]+)")


def create_doc(
    token: str,
    title: str,
    *,
    folder_token: str = "",
) -> object:
    client = get_client()
    body_builder = CreateDocumentRequestBody.builder().title(title)
    if folder_token:
        body_builder = body_builder.folder_token(folder_token)
    request = (
        CreateDocumentRequest.builder()
        .request_body(body_builder.build())
        .build()
    )
    response = client.docx.v1.document.create(request, make_option(token))
    if not response.success():
        if response.code == 99991679:
            raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
        raise RuntimeError(
            f"创建文档失败: code={response.code}, msg={response.msg}"
        )
    return response.data.document


def get_doc(token: str, document_id: str) -> object:
    client = get_client()
    request = GetDocumentRequest.builder().document_id(document_id).build()
    response = client.docx.v1.document.get(request, make_option(token))
    if not response.success():
        if response.code == 99991679:
            raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
        raise RuntimeError(
            f"获取文档失败: code={response.code}, msg={response.msg}"
        )
    return response.data.document


def get_doc_raw_content(token: str, document_id: str, *, lang: int = 0) -> str:
    client = get_client()
    request = (
        RawContentDocumentRequest.builder()
        .document_id(document_id)
        .lang(lang)
        .build()
    )
    response = client.docx.v1.document.raw_content(request, make_option(token))
    if not response.success():
        if response.code == 99991679:
            raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
        raise RuntimeError(
            f"获取文档内容失败: code={response.code}, msg={response.msg}"
        )
    return response.data.content or ""


def list_wiki_spaces(token: str, *, page_size: int = 50) -> list:
    client = get_client()
    all_items: list = []
    page_token: str | None = None
    while True:
        builder = ListSpaceRequest.builder().page_size(page_size)
        if page_token:
            builder = builder.page_token(page_token)
        response = client.wiki.v2.space.list(builder.build(), make_option(token))
        if not response.success():
            if response.code == 99991679:
                raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
            raise RuntimeError(
                f"获取 Wiki 空间列表失败: code={response.code}, msg={response.msg}"
            )
        all_items.extend(response.data.items or [])
        if not response.data.has_more:
            break
        page_token = response.data.page_token
    return all_items


def get_wiki_space(token: str, space_id: str) -> object:
    client = get_client()
    request = GetSpaceRequest.builder().space_id(space_id).build()
    response = client.wiki.v2.space.get(request, make_option(token))
    if not response.success():
        if response.code == 99991679:
            raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
        raise RuntimeError(
            f"获取 Wiki 空间失败: code={response.code}, msg={response.msg}"
        )
    return response.data.space


def get_wiki_node(token: str, space_id: str, node_token: str) -> object:
    client = get_client()
    request = (
        GetNodeSpaceRequest.builder()
        .token(node_token)
        .obj_type("wiki")
        .build()
    )
    response = client.wiki.v2.space.get_node(request, make_option(token))
    if not response.success():
        if response.code == 99991679:
            raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
        raise RuntimeError(
            f"获取 Wiki 节点失败: code={response.code}, msg={response.msg}"
        )
    return response.data.node


def list_wiki_nodes(
    token: str,
    space_id: str,
    *,
    parent_node_token: str = "",
    page_size: int = 50,
) -> list:
    client = get_client()
    all_items: list = []
    page_token: str | None = None
    while True:
        builder = (
            ListSpaceNodeRequest.builder()
            .space_id(space_id)
            .page_size(page_size)
        )
        if parent_node_token:
            builder = builder.parent_node_token(parent_node_token)
        if page_token:
            builder = builder.page_token(page_token)
        response = client.wiki.v2.space_node.list(builder.build(), make_option(token))
        if not response.success():
            if response.code == 99991679:
                raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
            raise RuntimeError(
                f"获取 Wiki 节点列表失败: code={response.code}, msg={response.msg}"
            )
        all_items.extend(response.data.items or [])
        if not response.data.has_more:
            break
        page_token = response.data.page_token
    return all_items


def create_wiki_node(
    token: str,
    space_id: str,
    obj_type: str,
    title: str,
    *,
    parent_node_token: str = "",
) -> object:
    client = get_client()
    node_builder = (
        Node.builder()
        .obj_type(obj_type)
        .title(title)
    )
    if parent_node_token:
        node_builder = node_builder.parent_node_token(parent_node_token)
    request = (
        CreateSpaceNodeRequest.builder()
        .space_id(space_id)
        .request_body(node_builder.build())
        .build()
    )
    response = client.wiki.v2.space_node.create(request, make_option(token))
    if not response.success():
        if response.code == 99991679:
            raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
        raise RuntimeError(
            f"创建 Wiki 节点失败: code={response.code}, msg={response.msg}"
        )
    return response.data.node


def move_doc_to_wiki(
    token: str,
    space_id: str,
    parent_wiki_token: str,
    obj_type: str,
    obj_token: str,
) -> object:
    client = get_client()
    request = (
        MoveDocsToWikiSpaceNodeRequest.builder()
        .space_id(space_id)
        .request_body(
            MoveDocsToWikiSpaceNodeRequestBody.builder()
            .parent_wiki_token(parent_wiki_token)
            .obj_type(obj_type)
            .obj_token(obj_token)
            .build()
        )
        .build()
    )
    response = client.wiki.v2.space_node.move_docs_to_wiki(request, make_option(token))
    if not response.success():
        if response.code == 99991679:
            raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
        raise RuntimeError(
            f"移动文档到 Wiki 失败: code={response.code}, msg={response.msg}"
        )
    return response.data


def get_wiki_node_by_url(token: str, url: str) -> dict:
    """Resolve a Feishu wiki URL to its node info in one call.

    Extracts the node_token from the URL path (``/wiki/{node_token}``),
    calls the wiki get_node API, and returns a plain dict so callers never
    need to handle the two-step resolution manually.

    Returns dict with keys: node_token, obj_token, obj_type, title, space_id.
    obj_token + obj_type are what you pass to docx / sheets / bitable APIs.
    """
    match = _WIKI_URL_RE.search(url)
    if not match:
        raise ValueError(f"无法从 URL 中提取 wiki node_token: {url!r}")
    node_token = match.group(1)
    node = get_wiki_node(token, "", node_token)
    return {
        "node_token": getattr(node, "node_token", node_token),
        "obj_token": getattr(node, "obj_token", ""),
        "obj_type": getattr(node, "obj_type", ""),
        "title": getattr(node, "title", ""),
        "space_id": getattr(node, "space_id", ""),
    }


def get_doc_blocks(token: str, document_id: str) -> list[dict]:
    """Return all blocks of a docx document as a list of plain dicts.

    Each dict contains at minimum: block_id, block_type, parent_id, children.
    Type-specific payload (text elements, code content, etc.) is included
    under a key matching the block type name when present.

    Handles pagination automatically (page_size=500 per request).
    """
    client = get_client()
    all_blocks: list[dict] = []
    page_token: str | None = None

    while True:
        builder = (
            ListDocumentBlockRequest.builder()
            .document_id(document_id)
            .page_size(500)
            .document_revision_id(-1)
        )
        if page_token:
            builder = builder.page_token(page_token)
        response = client.docx.v1.document_block.list(builder.build(), make_option(token))
        if not response.success():
            if response.code == 99991679:
                raise InsufficientScopeError(MODULE_SCOPES.get("document", []), module="document")
            raise RuntimeError(
                f"获取文档 blocks 失败: code={response.code}, msg={response.msg}"
            )
        for blk in response.data.items or []:
            block_type = getattr(blk, "block_type", 0)
            entry: dict = {
                "block_id": getattr(blk, "block_id", ""),
                "block_type": block_type,
                "parent_id": getattr(blk, "parent_id", ""),
                "children": list(getattr(blk, "children", None) or []),
            }
            _BLOCK_TYPE_NAMES = {
                1: "page", 2: "text", 3: "heading1", 4: "heading2", 5: "heading3",
                6: "heading4", 7: "heading5", 8: "heading6", 9: "heading7",
                10: "heading8", 11: "heading9", 12: "bullet", 13: "ordered",
                14: "code", 15: "quote", 17: "todo", 18: "bitable", 19: "callout",
                20: "chat_card", 21: "diagram", 22: "divider", 23: "file",
                24: "grid", 25: "grid_column", 26: "iframe", 27: "image",
                28: "isv", 29: "mindnote", 30: "sheet", 31: "table",
                32: "table_cell", 33: "view", 34: "undefined",
            }
            type_name = _BLOCK_TYPE_NAMES.get(block_type)
            if type_name and hasattr(blk, type_name):
                payload = getattr(blk, type_name)
                if payload is not None:
                    entry[type_name] = payload
            all_blocks.append(entry)

        if not response.data.has_more:
            break
        page_token = response.data.page_token

    return all_blocks
