#!/usr/bin/env python3
"""Import a Markdown/HTML file into Feishu Docx via blocks/convert + descendant/create.

Example:
  python3 scripts/import_markdown_to_feishu.py \
    --input-md docs/feishu/get_user_access_token.md \
    --title "获取 user_access_token" \
    --folder-token WmWvfSkMJlj2IEdMwCZcGpjinRe \
    --feishu-user-token "$FEISHU_USER_ACCESS_TOKEN"
"""

from __future__ import annotations

import argparse
import json
import os
import socket
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any, Dict, List

FEISHU_BASE = "https://open.feishu.cn/open-apis"
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_INPUT = ROOT / "docs" / "feishu" / "get_user_access_token.md"


class ApiError(RuntimeError):
    """Raised when Feishu API request fails."""


def http_json(
    method: str,
    url: str,
    headers: Dict[str, str],
    payload: Dict[str, Any] | None = None,
    timeout: float = 30.0,
    retries: int = 3,
) -> Dict[str, Any]:
    data = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8")
    last_err: Exception | None = None
    for i in range(retries):
        req = urllib.request.Request(url, data=data, method=method, headers=headers)
        try:
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                body = resp.read().decode("utf-8")
                return json.loads(body) if body else {}
        except urllib.error.HTTPError as e:
            body = ""
            try:
                body = e.read().decode("utf-8", errors="replace")
            except Exception:
                pass
            raise ApiError(f"HTTP {e.code} {method} {url}: {body}") from e
        except (urllib.error.URLError, TimeoutError, socket.timeout) as e:
            last_err = e
            if i < retries - 1:
                time.sleep(0.8 * (i + 1))
                continue
            raise ApiError(f"Network error {method} {url}: {e}") from e
    raise ApiError(f"Request failed {method} {url}: {last_err}")


def feishu_api(
    access_token: str,
    method: str,
    path: str,
    payload: Dict[str, Any] | None = None,
) -> Dict[str, Any]:
    url = f"{FEISHU_BASE}/{path.lstrip('/')}"
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json; charset=utf-8",
    }
    res = http_json(method, url, headers, payload)
    if isinstance(res, dict) and res.get("code", 0) != 0:
        raise ApiError(f"Feishu API {method} {path} failed: {json.dumps(res, ensure_ascii=False)}")
    return res


def create_doc(access_token: str, title: str, folder_token: str | None) -> tuple[str, str]:
    payload: Dict[str, Any] = {"title": title}
    if folder_token:
        payload["folder_token"] = folder_token
    res = feishu_api(access_token, "POST", "docx/v1/documents", payload)
    data = res.get("data", {})

    # Compatible with multiple response shapes.
    doc_id = (
        data.get("document_id")
        or (data.get("document") or {}).get("document_id")
        or (data.get("document") or {}).get("doc_id")
    )
    if not isinstance(doc_id, str) or not doc_id:
        raise ApiError(f"Cannot parse document_id: {json.dumps(res, ensure_ascii=False)}")

    doc_url = (
        data.get("url")
        or data.get("document_url")
        or (data.get("document") or {}).get("url")
        or (data.get("document") or {}).get("document_url")
        or f"https://open.feishu.cn/document/u{doc_id}"
    )
    return doc_id, doc_url


def remove_table_merge_info(block: Dict[str, Any]) -> None:
    if block.get("block_type") == 31 and isinstance(block.get("table"), dict):
        prop = block["table"].get("property")
        if isinstance(prop, dict):
            prop.pop("merge_info", None)


def convert_markdown_to_descendants(access_token: str, content_type: str, content: str) -> tuple[List[str], List[Dict[str, Any]]]:
    res = feishu_api(
        access_token,
        "POST",
        "docx/v1/documents/blocks/convert",
        {"content_type": content_type, "content": content},
    )
    data = res.get("data", {})
    first_level = list(data.get("first_level_block_ids") or [])
    blocks = list(data.get("blocks") or [])
    if not first_level or not blocks:
        raise ApiError("Convert API returned empty blocks.")

    descendants: List[Dict[str, Any]] = []
    for raw in blocks:
        if not isinstance(raw, dict):
            continue
        block_type = raw.get("block_type")
        # Page block cannot be inserted as descendant payload.
        if block_type == 1:
            continue
        b: Dict[str, Any] = {}
        for k, v in raw.items():
            if k in {"parent_id", "revision_id"}:
                continue
            b[k] = v
        remove_table_merge_info(b)
        descendants.append(b)
    return first_level, descendants


def create_descendants(
    access_token: str,
    document_id: str,
    children_id: List[str],
    descendants: List[Dict[str, Any]],
) -> Dict[str, Any]:
    if len(descendants) > 1000:
        raise ApiError("Descendants exceed 1000 blocks; split document before importing.")

    payload = {
        "index": 0,
        "children_id": children_id,
        "descendants": descendants,
    }
    path = f"docx/v1/documents/{document_id}/blocks/{document_id}/descendant?document_revision_id=-1"
    return feishu_api(access_token, "POST", path, payload)


def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(description="Import Markdown/HTML file into Feishu Docx.")
    p.add_argument("--input-md", default=str(DEFAULT_INPUT), help="Path to markdown/html file")
    p.add_argument("--content-type", default="markdown", choices=["markdown", "html"], help="Input content type")
    p.add_argument("--title", default="导入文档", help="Feishu document title")
    p.add_argument("--folder-token", default=None, help="Target Feishu folder token")
    p.add_argument(
        "--feishu-user-token",
        default=os.getenv("FEISHU_USER_ACCESS_TOKEN"),
        help="Feishu user_access_token",
    )
    return p.parse_args()


def main() -> int:
    args = parse_args()
    if not args.feishu_user_token:
        print("ERROR: Missing --feishu-user-token / FEISHU_USER_ACCESS_TOKEN")
        return 2

    input_path = Path(args.input_md)
    if not input_path.exists():
        print(f"ERROR: File not found: {input_path}")
        return 2

    content = input_path.read_text(encoding="utf-8")
    if not content.strip():
        print(f"ERROR: Empty file: {input_path}")
        return 2

    print(f"input: {input_path}")
    print(f"title: {args.title}")
    print(f"folder: {args.folder_token or '(none)'}")
    print("creating doc...")
    doc_id, doc_url = create_doc(args.feishu_user_token, args.title, args.folder_token)
    print(f"doc_id: {doc_id}")

    print("converting markdown/html to blocks...")
    first_level, descendants = convert_markdown_to_descendants(args.feishu_user_token, args.content_type, content)
    print(f"converted: first_level={len(first_level)}, descendants={len(descendants)}")

    print("writing descendants...")
    create_descendants(args.feishu_user_token, doc_id, first_level, descendants)

    print("done")
    print(f"url: {doc_url}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
