from __future__ import annotations

import json
import os
import sys

import click

from . import auth as _auth
from . import bitable as _bitable
from . import calendar as _calendar
from . import contact as _contact
from . import document as _document
from . import drive as _drive
from . import mail as _mail
from . import message as _message
from . import minutes as _minutes
from . import sheets as _sheets
from . import task as _task
from ._scopes import InsufficientScopeError


@click.group()
def main():
    pass


@main.group()
def auth():
    pass


@auth.command("login")
@click.option("--no-browser", is_flag=True, help="不自动打开浏览器，打印 URL 手动访问")
@click.option("--service-url", envvar="FEISHU_SERVICE_URL", default=None, help="ChatGPT 服务端地址")
def auth_login(no_browser: bool, service_url: str | None):
    if service_url:
        import os
        os.environ["FEISHU_SERVICE_URL"] = service_url
    try:
        token = _auth.login(open_browser=not no_browser)
        click.echo(f"✅ 授权成功: {token[:12]}...")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@auth.command("status")
def auth_status():
    status = _auth.token_status()
    click.echo(json.dumps(status, ensure_ascii=False, indent=2))


@auth.command("logout")
def auth_logout():
    if _auth.logout():
        click.echo("✅ 已清除本地 token")
    else:
        click.echo("⚠️  未找到本地 token")


@auth.command("token")
def auth_token():
    try:
        token = _auth.get_token()
        click.echo(token)
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@auth.command("ensure")
@click.argument("modules", nargs=-1)
@click.option("--no-browser", is_flag=True, help="不自动打开浏览器，打印 URL 手动访问")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def auth_ensure(modules: tuple[str, ...], no_browser: bool, token: str | None):
    from ._scopes import scopes_for
    try:
        required = scopes_for(*modules) if modules else []
        new_token = _auth.ensure_scopes(required, open_browser=not no_browser, token=token)
        click.echo(f"✅ 授权完成: {new_token[:12]}...")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def message():
    pass


@message.command("send")
@click.option("--to", required=True, help="接收者 open_id / chat_id")
@click.option("--text", required=True, help="消息文本")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_send(to: str, text: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            msg_id = _message.send_text(t, to, text)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            msg_id = _message.send_text(t, to, text)
        click.echo(f"✅ 发送成功: {msg_id}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("reply")
@click.option("--message-id", required=True)
@click.option("--text", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_reply(message_id: str, text: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            reply_id = _message.reply_text(t, message_id, text)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            reply_id = _message.reply_text(t, message_id, text)
        click.echo(f"✅ 回复成功: {reply_id}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("delete")
@click.option("--message-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_delete(message_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            _message.delete(t, message_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            _message.delete(t, message_id)
        click.echo("✅ 已撤回")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("search-chats")
@click.option("--query", required=True, help="搜索关键词（群名称，支持模糊/拼音）")
@click.option("--max", "max_count", default=20, type=int)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_search_chats(query: str, max_count: int, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            chats = _message.search_chats(t, query, max_count=max_count)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            chats = _message.search_chats(t, query, max_count=max_count)
        click.echo(json.dumps(chats, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("list-chats")
@click.option("--max", "max_count", default=50, type=int)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_list_chats(max_count: int, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            chats = _message.list_chats(t, max_count=max_count)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            chats = _message.list_chats(t, max_count=max_count)
        click.echo(json.dumps(chats, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("get-chat")
@click.option("--chat-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_get_chat(chat_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            chat = _message.get_chat(t, chat_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            chat = _message.get_chat(t, chat_id)
        click.echo(json.dumps(chat, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("get-chat-members")
@click.option("--chat-id", required=True)
@click.option("--max", "max_count", default=100, type=int)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_get_chat_members(chat_id: str, max_count: int, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            members = _message.get_chat_members(t, chat_id, max_count=max_count)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            members = _message.get_chat_members(t, chat_id, max_count=max_count)
        click.echo(json.dumps(members, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("pin")
@click.option("--message-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_pin(message_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _message.pin_message(t, message_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _message.pin_message(t, message_id)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("unpin")
@click.option("--message-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_unpin(message_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _message.unpin_message(t, message_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _message.unpin_message(t, message_id)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@message.command("list-in-chat")
@click.option("--chat-id", required=True)
@click.option("--start", "start_time", default=None, help="开始时间（Unix 秒字符串）")
@click.option("--end", "end_time", default=None, help="结束时间（Unix 秒字符串）")
@click.option("--max", "max_count", default=100, type=int)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def message_list_in_chat(chat_id: str, start_time: str | None, end_time: str | None,
                          max_count: int, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            msgs = _message.list_messages_in_chat(t, chat_id, start_time=start_time,
                                                   end_time=end_time, max_count=max_count)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            msgs = _message.list_messages_in_chat(t, chat_id, start_time=start_time,
                                                   end_time=end_time, max_count=max_count)
        for m in msgs:
            click.echo(json.dumps({
                "message_id": getattr(m, "message_id", ""),
                "msg_type": getattr(m, "msg_type", ""),
                "sender": str(getattr(m, "sender", "")),
                "create_time": getattr(m, "create_time", ""),
            }, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def bitable():
    pass


@bitable.command("list-records")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--filter", "filter_expr", default="")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_list_records(app_token: str, table_id: str, filter_expr: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            records = _bitable.list_records(t, app_token, table_id, filter=filter_expr)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            records = _bitable.list_records(t, app_token, table_id, filter=filter_expr)
        for r in records:
            click.echo(json.dumps({"record_id": getattr(r, "record_id", ""), "fields": getattr(r, "fields", {})}, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("create-record")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--fields", required=True, help="JSON string of fields")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_create_record(app_token: str, table_id: str, fields: str, token: str | None):
    try:
        t = token or _auth.get_token()
        parsed = json.loads(fields)
        try:
            record = _bitable.create_record(t, app_token, table_id, parsed)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            record = _bitable.create_record(t, app_token, table_id, parsed)
        click.echo(f"✅ 创建成功: {getattr(record, 'record_id', record)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("list-fields")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_list_fields(app_token: str, table_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            fields = _bitable.list_fields(t, app_token, table_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            fields = _bitable.list_fields(t, app_token, table_id)
        click.echo(json.dumps(fields, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("create-field")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--field-name", required=True)
@click.option("--field-type", required=True, type=int, help="字段类型: 1=文本 2=数字 3=单选 4=多选 5=日期 7=复选框 11=人员 15=超链接 17=附件 18=关联 20=公式 21=双向关联 22=地理位置 23=群组 1001=创建时间 1002=修改时间 1003=创建人 1004=修改人")
@click.option("--ui-type", default="", help="UI 类型（可选）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_create_field(app_token: str, table_id: str, field_name: str, field_type: int,
                          ui_type: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            field = _bitable.create_field(t, app_token, table_id, field_name, field_type, ui_type=ui_type)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            field = _bitable.create_field(t, app_token, table_id, field_name, field_type, ui_type=ui_type)
        click.echo(json.dumps(field, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("delete-field")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--field-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_delete_field(app_token: str, table_id: str, field_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _bitable.delete_field(t, app_token, table_id, field_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _bitable.delete_field(t, app_token, table_id, field_id)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("list-views")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_list_views(app_token: str, table_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            views = _bitable.list_views(t, app_token, table_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            views = _bitable.list_views(t, app_token, table_id)
        click.echo(json.dumps(views, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("create-view")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--view-name", required=True)
@click.option("--view-type", default="grid", help="grid | kanban | gallery | gantt | form")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_create_view(app_token: str, table_id: str, view_name: str, view_type: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            view = _bitable.create_view(t, app_token, table_id, view_name, view_type=view_type)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            view = _bitable.create_view(t, app_token, table_id, view_name, view_type=view_type)
        click.echo(json.dumps(view, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("delete-view")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--view-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_delete_view(app_token: str, table_id: str, view_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _bitable.delete_view(t, app_token, table_id, view_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _bitable.delete_view(t, app_token, table_id, view_id)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@bitable.command("delete-table")
@click.option("--app-token", required=True)
@click.option("--table-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def bitable_delete_table(app_token: str, table_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _bitable.delete_table(t, app_token, table_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _bitable.delete_table(t, app_token, table_id)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def contact():
    pass


@contact.command("get-user")
@click.option("--user-id", required=True)
@click.option("--id-type", default="open_id")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def contact_get_user(user_id: str, id_type: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            user = _contact.get_user(t, user_id, user_id_type=id_type)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            user = _contact.get_user(t, user_id, user_id_type=id_type)
        click.echo(json.dumps({"name": getattr(user, "name", ""), "email": getattr(user, "email", "")}, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@contact.command("search")
@click.option("--query", required=True, help="搜索关键词（姓名）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def contact_search(query: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            users = _contact.search_user(t, query)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            users = _contact.search_user(t, query)
        for u in users:
            click.echo(json.dumps({
                "name": u.get("name", ""),
                "open_id": u.get("open_id", ""),
                "user_id": u.get("user_id", ""),
                "department_path": u.get("department_path", ""),
            }, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def document():
    pass


@document.command("create")
@click.option("--title", required=True)
@click.option("--folder-token", default="")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def document_create(title: str, folder_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            doc = _document.create_doc(t, title, folder_token=folder_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            doc = _document.create_doc(t, title, folder_token=folder_token)
        click.echo(f"✅ 创建成功: {getattr(doc, 'document_id', doc)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@document.command("get-content")
@click.option("--document-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def document_get_content(document_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            content = _document.get_doc_raw_content(t, document_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            content = _document.get_doc_raw_content(t, document_id)
        click.echo(content)
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@document.command("get-wiki-node")
@click.option("--url", required=True, help="飞书 wiki 页面 URL（含 /wiki/{node_token}）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def document_get_wiki_node(url: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            node = _document.get_wiki_node_by_url(t, url)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            node = _document.get_wiki_node_by_url(t, url)
        click.echo(json.dumps(node, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@document.command("get-blocks")
@click.option("--document-id", required=True, help="文档 obj_token（docx document_id）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def document_get_blocks(document_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            blocks = _document.get_doc_blocks(t, document_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            blocks = _document.get_doc_blocks(t, document_id)
        click.echo(json.dumps(blocks, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def sheets():
    pass


@sheets.command("get")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_get(spreadsheet_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.get_spreadsheet(t, spreadsheet_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.get_spreadsheet(t, spreadsheet_token)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("list-sheets")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_list_sheets(spreadsheet_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.list_sheets(t, spreadsheet_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.list_sheets(t, spreadsheet_token)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("create")
@click.option("--title", required=True, help="表格标题")
@click.option("--folder-token", default="", help="目标文件夹 token（不填则放到根目录）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_create(title: str, folder_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.create_spreadsheet(t, title, folder_token=folder_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.create_spreadsheet(t, title, folder_token=folder_token)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("read")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--sheet-id", required=True, help="工作表 sheet_id")
@click.option("--range", "range_", default="", help="单元格范围，例如 A1:D10（不填则读取整个工作表）")
@click.option("--raw", is_flag=True, default=False, help="富文本保留原始 segments 结构")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_read(spreadsheet_token: str, sheet_id: str, range_: str, raw: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.read_values(t, spreadsheet_token, sheet_id, range_, raw=raw)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.read_values(t, spreadsheet_token, sheet_id, range_, raw=raw)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("write")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--sheet-id", required=True, help="工作表 sheet_id")
@click.option("--range", "range_", required=True, help="起始单元格范围，例如 A1")
@click.option("--values", required=True, help="JSON 二维数组，例如 '[[\"a\",\"b\"],[1,2]]'")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_write(spreadsheet_token: str, sheet_id: str, range_: str, values: str, token: str | None):
    try:
        t = token or _auth.get_token()
        parsed = json.loads(values)
        try:
            result = _sheets.write_values(t, spreadsheet_token, sheet_id, range_, parsed)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.write_values(t, spreadsheet_token, sheet_id, range_, parsed)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("append")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--sheet-id", required=True, help="工作表 sheet_id")
@click.option("--values", required=True, help="JSON 二维数组，例如 '[[\"a\",\"b\"],[1,2]]'")
@click.option("--insert-data-option", default="OVERWRITE", help="OVERWRITE 或 INSERT_ROWS")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_append(spreadsheet_token: str, sheet_id: str, values: str,
                  insert_data_option: str, token: str | None):
    try:
        t = token or _auth.get_token()
        parsed = json.loads(values)
        try:
            result = _sheets.append_values(t, spreadsheet_token, sheet_id, parsed,
                                           insert_data_option=insert_data_option)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.append_values(t, spreadsheet_token, sheet_id, parsed,
                                           insert_data_option=insert_data_option)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("clear")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--sheet-id", required=True, help="工作表 sheet_id")
@click.option("--range", "range_", required=True, help="要清除的单元格范围，例如 A1:D10")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_clear(spreadsheet_token: str, sheet_id: str, range_: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.clear_values(t, spreadsheet_token, sheet_id, range_)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.clear_values(t, spreadsheet_token, sheet_id, range_)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("batch-read")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--ranges", required=True, help="逗号分隔的范围列表，例如 sheetId1!A1:B2,sheetId2!C1:D3")
@click.option("--raw", is_flag=True, default=False)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_batch_read(spreadsheet_token: str, ranges: str, raw: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        ranges_list = [r.strip() for r in ranges.split(",")]
        try:
            result = _sheets.batch_read_values(t, spreadsheet_token, ranges_list, raw=raw)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.batch_read_values(t, spreadsheet_token, ranges_list, raw=raw)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("batch-write")
@click.option("--spreadsheet-token", required=True, help="电子表格 token（sht...）")
@click.option("--range-values", required=True, help='JSON 数组，例如 \'[{"range":"sheetId!A1","values":[[1,2]]}]\'')
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_batch_write(spreadsheet_token: str, range_values: str, token: str | None):
    try:
        t = token or _auth.get_token()
        parsed = json.loads(range_values)
        try:
            result = _sheets.batch_write_values(t, spreadsheet_token, parsed)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.batch_write_values(t, spreadsheet_token, parsed)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("merge-cells")
@click.option("--spreadsheet-token", required=True)
@click.option("--sheet-id", required=True)
@click.option("--range", "range_", required=True, help="要合并的范围，例如 A1:C3")
@click.option("--merge-type", default="MERGE_ALL", help="MERGE_ALL | MERGE_ROWS | MERGE_COLUMNS")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_merge_cells(spreadsheet_token: str, sheet_id: str, range_: str, merge_type: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.merge_cells(t, spreadsheet_token, sheet_id, range_, merge_type=merge_type)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.merge_cells(t, spreadsheet_token, sheet_id, range_, merge_type=merge_type)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("unmerge-cells")
@click.option("--spreadsheet-token", required=True)
@click.option("--sheet-id", required=True)
@click.option("--range", "range_", required=True, help="要取消合并的范围")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_unmerge_cells(spreadsheet_token: str, sheet_id: str, range_: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.unmerge_cells(t, spreadsheet_token, sheet_id, range_)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.unmerge_cells(t, spreadsheet_token, sheet_id, range_)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("add-sheet")
@click.option("--spreadsheet-token", required=True)
@click.option("--title", required=True, help="新工作表标题")
@click.option("--index", default=None, type=int, help="插入位置（从 0 开始）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_add_sheet(spreadsheet_token: str, title: str, index: int | None, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.add_sheet(t, spreadsheet_token, title, index=index)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.add_sheet(t, spreadsheet_token, title, index=index)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("delete-sheet")
@click.option("--spreadsheet-token", required=True)
@click.option("--sheet-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_delete_sheet(spreadsheet_token: str, sheet_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.delete_sheet(t, spreadsheet_token, sheet_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.delete_sheet(t, spreadsheet_token, sheet_id)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@sheets.command("copy-sheet")
@click.option("--spreadsheet-token", required=True)
@click.option("--sheet-id", required=True)
@click.option("--dest-title", default=None, help="复制后的工作表标题")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def sheets_copy_sheet(spreadsheet_token: str, sheet_id: str, dest_title: str | None, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _sheets.copy_sheet(t, spreadsheet_token, sheet_id, dest_title=dest_title)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _sheets.copy_sheet(t, spreadsheet_token, sheet_id, dest_title=dest_title)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def drive():
    pass


@drive.command("list-folder")
@click.option("--folder-token", default="", help="文件夹 token，不填则为根目录")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_list_folder(folder_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            items = _drive.list_folder(t, folder_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            items = _drive.list_folder(t, folder_token)
        for item in items:
            click.echo(json.dumps({
                "name": getattr(item, "name", ""),
                "token": getattr(item, "token", ""),
                "type": getattr(item, "type", ""),
            }, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("upload")
@click.option("--parent-token", required=True, help="目标文件夹 token")
@click.option("--file", "file_path", required=True, type=click.Path(exists=True), help="本地文件路径")
@click.option("--name", default=None, help="上传后的文件名，默认使用本地文件名")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_upload(parent_token: str, file_path: str, name: str | None, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            file_token = _drive.upload(t, parent_token, file_path, file_name=name)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            file_token = _drive.upload(t, parent_token, file_path, file_name=name)
        click.echo(f"✅ 上传成功: {file_token}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("download")
@click.option("--file-token", required=True)
@click.option("--output", required=True, help="本地保存路径")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_download(file_token: str, output: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            path = _drive.download(t, file_token, output)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            path = _drive.download(t, file_token, output)
        click.echo(f"✅ 下载成功: {path}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("move")
@click.option("--file-token", required=True)
@click.option("--folder-token", required=True, help="目标文件夹 token")
@click.option("--type", "file_type", default="file")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_move(file_token: str, folder_token: str, file_type: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            _drive.move(t, file_token, folder_token=folder_token, type=file_type)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            _drive.move(t, file_token, folder_token=folder_token, type=file_type)
        click.echo("✅ 移动成功")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("delete")
@click.option("--file-token", required=True)
@click.option("--type", "file_type", default="file")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_delete(file_token: str, file_type: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            _drive.delete(t, file_token, type=file_type)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            _drive.delete(t, file_token, type=file_type)
        click.echo("✅ 删除成功")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("create-folder")
@click.option("--parent-token", required=True)
@click.option("--name", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_create_folder(parent_token: str, name: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _drive.create_folder(t, parent_token, name)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _drive.create_folder(t, parent_token, name)
        click.echo(f"✅ 创建成功: {getattr(result, 'token', result)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("get-meta")
@click.option("--doc-token", required=True, help="文件 token（可多次传入，逗号分隔）")
@click.option("--doc-type", required=True, help="文件类型: doc | sheet | bitable | file | wiki | docx（可多次传入，逗号分隔）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_get_meta(doc_token: str, doc_type: str, token: str | None):
    try:
        t = token or _auth.get_token()
        tokens = [x.strip() for x in doc_token.split(",")]
        types = [x.strip() for x in doc_type.split(",")]
        if len(types) == 1:
            types = types * len(tokens)
        items = [{"doc_token": tok, "doc_type": typ} for tok, typ in zip(tokens, types)]
        try:
            result = _drive.get_file_meta(t, items)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _drive.get_file_meta(t, items)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("search")
@click.option("--query", required=True, help="搜索关键词")
@click.option("--max", "max_count", default=20, type=int, help="最多返回条数")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_search(query: str, max_count: int, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _drive.search_files(t, query, max_count=max_count)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _drive.search_files(t, query, max_count=max_count)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("export")
@click.option("--file-token", required=True, help="云文档 token（doc/sheet/bitable token）")
@click.option("--file-type", required=True, help="文档类型: doc | docx | sheet | bitable")
@click.option("--file-extension", required=True, help="导出格式: xlsx | csv | docx | pdf")
@click.option("--output", required=True, help="本地保存路径，例如 ./output.xlsx")
@click.option("--timeout", "timeout_sec", default=120, type=float, help="等待超时秒数（默认 120）")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_export(file_token: str, file_type: str, file_extension: str,
                 output: str, timeout_sec: float, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            path = _drive.export_file(t, file_token, file_type, file_extension, output,
                                      timeout=timeout_sec)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            path = _drive.export_file(t, file_token, file_type, file_extension, output,
                                      timeout=timeout_sec)
        click.echo(f"✅ 导出成功: {path}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@drive.command("set-permission")
@click.option("--file-token", required=True)
@click.option("--file-type", required=True, help="doc | sheet | file | wiki | bitable | docx")
@click.option("--member-type", required=True, help="user | chat | department | group")
@click.option("--member-id", required=True)
@click.option("--perm", required=True, help="view | edit | full_access")
@click.option("--notify", is_flag=True, default=False, help="发送通知")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def drive_set_permission(file_token: str, file_type: str, member_type: str,
                          member_id: str, perm: str, notify: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _drive.set_permission(t, file_token, file_type, member_type, member_id, perm,
                                           need_notification=notify)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _drive.set_permission(t, file_token, file_type, member_type, member_id, perm,
                                           need_notification=notify)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def calendar():
    pass


@calendar.command("primary")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_primary(token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            cal = _calendar.get_primary_calendar(t)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            cal = _calendar.get_primary_calendar(t)
        click.echo(json.dumps({
            "calendar_id": getattr(cal, "calendar_id", ""),
            "summary": getattr(cal, "summary", ""),
        }, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@calendar.command("list-events")
@click.option("--calendar-id", required=True)
@click.option("--start", "start_time", default=None, help="开始时间 (Unix 时间戳字符串)")
@click.option("--end", "end_time", default=None, help="结束时间 (Unix 时间戳字符串)")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_list_events(calendar_id: str, start_time: str | None, end_time: str | None, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            events = _calendar.list_events(t, calendar_id, start_time=start_time, end_time=end_time)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            events = _calendar.list_events(t, calendar_id, start_time=start_time, end_time=end_time)
        for ev in events:
            click.echo(json.dumps({
                "event_id": getattr(ev, "event_id", ""),
                "summary": getattr(ev, "summary", ""),
                "start_time": str(getattr(ev, "start_time", "")),
                "end_time": str(getattr(ev, "end_time", "")),
            }, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@calendar.command("create-event")
@click.option("--calendar-id", required=True)
@click.option("--summary", required=True)
@click.option("--start", "start_time", required=True, help="开始时间 Unix 时间戳（毫秒字符串）")
@click.option("--end", "end_time", required=True, help="结束时间 Unix 时间戳（毫秒字符串）")
@click.option("--description", default="")
@click.option("--location", default="")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_create_event(calendar_id: str, summary: str, start_time: str, end_time: str,
                          description: str, location: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            ev = _calendar.create_event(
                t, calendar_id, summary,
                start_time=start_time, end_time=end_time,
                description=description, location=location,
            )
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            ev = _calendar.create_event(
                t, calendar_id, summary,
                start_time=start_time, end_time=end_time,
                description=description, location=location,
            )
        click.echo(f"✅ 创建成功: {getattr(ev, 'event_id', ev)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@calendar.command("update-event")
@click.option("--calendar-id", required=True)
@click.option("--event-id", required=True)
@click.option("--summary", default=None)
@click.option("--start", "start_time", default=None)
@click.option("--end", "end_time", default=None)
@click.option("--description", default=None)
@click.option("--location", default=None)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_update_event(calendar_id: str, event_id: str, summary: str | None,
                          start_time: str | None, end_time: str | None,
                          description: str | None, location: str | None, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            ev = _calendar.update_event(
                t, calendar_id, event_id,
                summary=summary, start_time=start_time, end_time=end_time,
                description=description, location=location,
            )
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            ev = _calendar.update_event(
                t, calendar_id, event_id,
                summary=summary, start_time=start_time, end_time=end_time,
                description=description, location=location,
            )
        click.echo(f"✅ 更新成功: {getattr(ev, 'event_id', ev)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@calendar.command("delete-event")
@click.option("--calendar-id", required=True)
@click.option("--event-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_delete_event(calendar_id: str, event_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            _calendar.delete_event(t, calendar_id, event_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            _calendar.delete_event(t, calendar_id, event_id)
        click.echo("✅ 删除成功")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@calendar.command("add-attendees")
@click.option("--calendar-id", required=True)
@click.option("--event-id", required=True)
@click.option("--user-ids", required=True, help="逗号分隔的参与者 open_id 列表")
@click.option("--no-notify", is_flag=True, default=False, help="不发送通知")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_add_attendees(calendar_id: str, event_id: str, user_ids: str,
                            no_notify: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        ids = [x.strip() for x in user_ids.split(",")]
        try:
            result = _calendar.add_attendees(t, calendar_id, event_id, ids,
                                             need_notification=not no_notify)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _calendar.add_attendees(t, calendar_id, event_id, ids,
                                             need_notification=not no_notify)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@calendar.command("remove-attendees")
@click.option("--calendar-id", required=True)
@click.option("--event-id", required=True)
@click.option("--attendee-ids", required=True, help="逗号分隔的 attendee_id 列表")
@click.option("--no-notify", is_flag=True, default=False, help="不发送通知")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_remove_attendees(calendar_id: str, event_id: str, attendee_ids: str,
                               no_notify: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        ids = [x.strip() for x in attendee_ids.split(",")]
        try:
            result = _calendar.remove_attendees(t, calendar_id, event_id, ids,
                                                need_notification=not no_notify)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _calendar.remove_attendees(t, calendar_id, event_id, ids,
                                                need_notification=not no_notify)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@calendar.command("freebusy")
@click.option("--user-id", required=True, help="被查询用户的 open_id")
@click.option("--time-min", required=True, help="起始时间 RFC3339 格式，例如 2026-03-17T00:00:00+08:00")
@click.option("--time-max", required=True, help="结束时间 RFC3339 格式")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def calendar_freebusy(user_id: str, time_min: str, time_max: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _calendar.query_freebusy(t, user_id, time_min, time_max)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _calendar.query_freebusy(t, user_id, time_min, time_max)
        click.echo(json.dumps(result, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def minutes():
    pass


@minutes.command("get")
@click.option("--minute-token", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def minutes_get(minute_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            m = _minutes.get_minute(t, minute_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            m = _minutes.get_minute(t, minute_token)
        click.echo(json.dumps({
            "title": getattr(m, "title", ""),
            "owner_id": getattr(m, "owner_id", ""),
            "create_time": getattr(m, "create_time", ""),
        }, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@minutes.command("transcript")
@click.option("--minute-token", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def minutes_transcript(minute_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            paragraphs = _minutes.get_transcript(t, minute_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            paragraphs = _minutes.get_transcript(t, minute_token)
        for p in paragraphs:
            click.echo(json.dumps({
                "speaker": str(getattr(p, "speaker", "")),
                "start_time": getattr(p, "start_time", ""),
                "content": getattr(p, "content", ""),
            }, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@minutes.command("statistics")
@click.option("--minute-token", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def minutes_statistics(minute_token: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            stats = _minutes.get_statistics(t, minute_token)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            stats = _minutes.get_statistics(t, minute_token)
        click.echo(json.dumps({
            "total_time": getattr(stats, "total_time", ""),
            "attendee_count": getattr(stats, "attendee_count", ""),
        }, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def task():
    pass


@task.command("list")
@click.option("--completed", is_flag=True, default=False, help="只显示已完成任务")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def task_list(completed: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            tasks = _task.list_tasks(t, completed=completed if completed else None)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            tasks = _task.list_tasks(t, completed=completed if completed else None)
        for tk in tasks:
            click.echo(json.dumps({
                "guid": getattr(tk, "guid", ""),
                "summary": getattr(tk, "summary", ""),
                "completed_at": getattr(tk, "completed_at", ""),
            }, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@task.command("get")
@click.option("--task-guid", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def task_get(task_guid: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            tk = _task.get_task(t, task_guid)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            tk = _task.get_task(t, task_guid)
        click.echo(json.dumps({
            "guid": getattr(tk, "guid", ""),
            "summary": getattr(tk, "summary", ""),
            "description": getattr(tk, "description", ""),
            "completed_at": getattr(tk, "completed_at", ""),
        }, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@task.command("create")
@click.option("--summary", required=True)
@click.option("--due", default=None, help="截止时间 Unix 毫秒时间戳字符串")
@click.option("--description", default="")
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def task_create(summary: str, due: str | None, description: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            tk = _task.create_task(t, summary, due=due, description=description)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            tk = _task.create_task(t, summary, due=due, description=description)
        click.echo(f"✅ 创建成功: {getattr(tk, 'guid', tk)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@task.command("update")
@click.option("--task-guid", required=True)
@click.option("--summary", default=None)
@click.option("--due", default=None)
@click.option("--description", default=None)
@click.option("--complete", "completed", is_flag=True, default=False)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def task_update(task_guid: str, summary: str | None, due: str | None,
                description: str | None, completed: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            tk = _task.update_task(
                t, task_guid,
                summary=summary, due=due, description=description,
                completed=True if completed else None,
            )
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            tk = _task.update_task(
                t, task_guid,
                summary=summary, due=due, description=description,
                completed=True if completed else None,
            )
        click.echo(f"✅ 更新成功: {getattr(tk, 'guid', tk)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@task.command("delete")
@click.option("--task-guid", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def task_delete(task_guid: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            _task.delete_task(t, task_guid)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            _task.delete_task(t, task_guid)
        click.echo("✅ 删除成功")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@task.command("complete")
@click.option("--task-guid", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def task_complete(task_guid: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            tk = _task.complete_task(t, task_guid)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            tk = _task.complete_task(t, task_guid)
        click.echo(f"✅ 已完成: {getattr(tk, 'guid', tk)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@main.group()
def mail():
    pass


@mail.command("list")
@click.option("--folder", "folder_id", default="INBOX")
@click.option("--unread", is_flag=True, default=False)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def mail_list(folder_id: str, unread: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            msgs = _mail.list_messages(t, folder_id=folder_id, only_unread=unread)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            msgs = _mail.list_messages(t, folder_id=folder_id, only_unread=unread)
        for msg in msgs:
            click.echo(json.dumps({
                "message_id": getattr(msg, "message_id", ""),
                "subject": getattr(msg, "subject", ""),
                "from": str(getattr(msg, "from_", "")),
            }, ensure_ascii=False))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@mail.command("get")
@click.option("--message-id", required=True)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def mail_get(message_id: str, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            msg = _mail.get_message(t, message_id)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            msg = _mail.get_message(t, message_id)
        click.echo(json.dumps({
            "message_id": getattr(msg, "message_id", ""),
            "subject": getattr(msg, "subject", ""),
            "body_plain_text": getattr(msg, "body_plain_text", ""),
        }, ensure_ascii=False, indent=2))
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)


@mail.command("send")
@click.option("--to", required=True, help="收件人邮箱地址")
@click.option("--subject", required=True)
@click.option("--body", required=True)
@click.option("--html", is_flag=True, default=False)
@click.option("--token", envvar="FEISHU_USER_TOKEN", default=None)
def mail_send(to: str, subject: str, body: str, html: bool, token: str | None):
    try:
        t = token or _auth.get_token()
        try:
            result = _mail.send_mail(t, to=to, subject=subject, body=body, html=html)
        except InsufficientScopeError as e:
            t = _auth.ensure_scopes(e.missing)
            result = _mail.send_mail(t, to=to, subject=subject, body=body, html=html)
        click.echo(f"✅ 发送成功: {getattr(result, 'message_id', result)}")
    except Exception as e:
        click.echo(f"❌ {e}", err=True)
        sys.exit(1)
