#!/usr/bin/env python3
"""OpenClaw downstream meeting-minutes orchestrator.

This script implements the reusable half of the OpenClaw flow:

minute_token -> lark-cli minutes/vc/docs -> Markdown -> JSON result

It intentionally does not upload audio into Feishu Minutes. That step must be
handled by an upstream ingest adapter which returns a minute_token.
"""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any


REQUIRED_SCOPES = (
    "vc:note:read "
    "minutes:minutes:readonly "
    "minutes:minutes.artifacts:read "
    "minutes:minutes.transcript:export "
    "docx:document:create"
)


class CLIError(Exception):
    def __init__(self, message: str, code: str = "cli_error", detail: Any = None):
        super().__init__(message)
        self.code = code
        self.detail = detail


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Build meeting minutes Markdown from an existing minute_token."
    )
    parser.add_argument("--minute-token", required=True, help="Feishu minute_token")
    parser.add_argument("--job-id", required=True, help="OpenClaw job ID")
    parser.add_argument("--title", help="Override meeting title")
    parser.add_argument("--folder-token", help="Target Feishu folder token")
    parser.add_argument(
        "--output-dir",
        default=".openclaw-meeting-minutes",
        help="Directory for transcript/artifact files",
    )
    parser.add_argument(
        "--config-dir",
        help="Override LARKSUITE_CLI_CONFIG_DIR for per-user isolation",
    )
    parser.add_argument(
        "--lark-cli-bin",
        default="lark-cli",
        help="Path to the lark-cli executable",
    )
    return parser.parse_args()


def build_env(config_dir: str | None) -> dict[str, str]:
    env = os.environ.copy()
    if config_dir:
        env["LARKSUITE_CLI_CONFIG_DIR"] = config_dir
    return env


def run_cli(
    cli_bin: str,
    env: dict[str, str],
    args: list[str],
    check: bool = True,
    cwd: Path | None = None,
) -> Any:
    proc = subprocess.run(
        [cli_bin] + args,
        capture_output=True,
        text=True,
        env=env,
        cwd=str(cwd) if cwd else None,
    )
    stdout = proc.stdout.strip()
    stderr = proc.stderr.strip()

    parsed: Any = None
    if stdout:
        try:
            parsed = json.loads(stdout)
        except json.JSONDecodeError as exc:
            raise CLIError(
                f"lark-cli returned non-JSON stdout: {stdout[:400]}",
                code="invalid_cli_output",
                detail={"stderr": stderr, "args": args},
            ) from exc

    if check and proc.returncode != 0:
        error_detail = parsed if isinstance(parsed, dict) else {"stderr": stderr}
        raise CLIError(
            f"lark-cli command failed: {' '.join(args)}",
            code="lark_cli_failed",
            detail=error_detail,
        )

    return parsed


def unwrap_payload(payload: Any) -> Any:
    if isinstance(payload, dict) and "ok" in payload:
        if not payload.get("ok", False):
            error = payload.get("error") or {}
            raise CLIError(
                error.get("message", "lark-cli returned an error envelope"),
                code=error.get("type", "lark_error"),
                detail=payload,
            )
        return payload.get("data")
    return payload


def ensure_auth(cli_bin: str, env: dict[str, str]) -> None:
    run_cli(cli_bin, env, ["auth", "check", "--scope", REQUIRED_SCOPES], check=True)


def minute_info(cli_bin: str, env: dict[str, str], minute_token: str) -> dict[str, Any]:
    payload = run_cli(
        cli_bin,
        env,
        [
            "api",
            "GET",
            f"/open-apis/minutes/v1/minutes/{minute_token}",
            "--as",
            "user",
            "--format",
            "json",
        ],
    )
    data = unwrap_payload(payload)
    if isinstance(data, dict) and "data" in data and isinstance(data["data"], dict):
        data = data["data"]
    if not isinstance(data, dict):
        raise CLIError("unexpected minutes.get payload", code="invalid_minutes_payload")
    minute = data.get("minute")
    if not isinstance(minute, dict):
        raise CLIError("minutes.get missing minute object", code="missing_minute")
    return minute


def notes_info(
    cli_bin: str, env: dict[str, str], minute_token: str, output_dir: Path
) -> dict[str, Any]:
    output_dir = output_dir.resolve()
    payload = run_cli(
        cli_bin,
        env,
        [
            "vc",
            "+notes",
            "--minute-tokens",
            minute_token,
            "--output-dir",
            "vc-notes-artifacts",
            "--format",
            "json",
        ],
        cwd=output_dir,
    )
    data = unwrap_payload(payload)
    if not isinstance(data, dict):
        raise CLIError("unexpected vc +notes payload", code="invalid_notes_payload")
    notes = data.get("notes")
    if not isinstance(notes, list) or not notes:
        raise CLIError("vc +notes returned no notes", code="missing_notes")
    first = notes[0]
    if not isinstance(first, dict):
        raise CLIError("invalid note item shape", code="invalid_note_item")
    if first.get("error"):
        raise CLIError(str(first["error"]), code="note_query_failed", detail=first)
    artifacts = first.get("artifacts")
    if isinstance(artifacts, dict):
        transcript_file = artifacts.get("transcript_file")
        if isinstance(transcript_file, str) and transcript_file:
            transcript_path = Path(transcript_file)
            if not transcript_path.is_absolute():
                artifacts["transcript_file"] = str(
                    (output_dir / transcript_path).resolve()
                )
    return first


def read_text(path: str | None) -> str:
    if not path:
        return ""
    p = Path(path)
    if not p.exists():
        return ""
    return p.read_text(encoding="utf-8").strip()


def transcript_excerpt(text: str, limit: int = 2000) -> str:
    if not text:
        return ""
    if len(text) <= limit:
        return text
    return text[:limit].rstrip() + "\n..."


def format_inline(value: Any) -> str:
    if value in (None, "", [], {}):
        return "暂无 AI 产物"
    if isinstance(value, str):
        return value.strip() or "暂无 AI 产物"
    if isinstance(value, list):
        items = [format_list_item(item) for item in value]
        items = [item for item in items if item]
        return "\n".join(items) if items else "暂无 AI 产物"
    if isinstance(value, dict):
        return "```json\n" + json.dumps(value, ensure_ascii=False, indent=2) + "\n```"
    return str(value)


def format_list_item(item: Any) -> str:
    if isinstance(item, str):
        return f"- {item}"
    if isinstance(item, dict):
        preferred = [
            item.get("title"),
            item.get("summary"),
            item.get("content"),
            item.get("text"),
            item.get("todo"),
            item.get("speaker"),
        ]
        text = next((str(v).strip() for v in preferred if v), "")
        if text:
            extras: list[str] = []
            for key in ("assignee", "owner", "deadline", "timestamp", "time"):
                if item.get(key):
                    extras.append(f"{key}: {item[key]}")
            if extras:
                return f"- {text} ({', '.join(extras)})"
            return f"- {text}"
        return "- " + json.dumps(item, ensure_ascii=False, sort_keys=True)
    return f"- {item}"


def build_markdown(
    title: str,
    minute_token: str,
    minutes_url: str,
    note: dict[str, Any],
    transcript_text: str,
    doc_url: str = "",
) -> str:
    artifacts = note.get("artifacts") or {}
    if not isinstance(artifacts, dict):
        artifacts = {}
    lines = [
        f"# {title}",
        "",
        "## 会议信息",
        "",
        f"- 标题：{title}",
        f"- minute_token：`{minute_token}`",
        f"- 妙记链接：{minutes_url or '暂无'}",
        f"- 纪要文档 Token：`{note.get('note_doc_token') or '暂无'}`",
        f"- 逐字稿文档 Token：`{note.get('verbatim_doc_token') or '暂无'}`",
        f"- 逐字稿文件：{artifacts.get('transcript_file') or '暂无'}",
        "",
        "## 摘要",
        "",
        format_inline(artifacts.get("summary")),
        "",
        "## 待办",
        "",
        format_inline(artifacts.get("todos")),
        "",
        "## 章节",
        "",
        format_inline(artifacts.get("chapters")),
        "",
        "## 逐字稿",
        "",
        transcript_text or "暂无 AI 产物",
        "",
        "## 原始飞书产物",
        "",
        f"- 妙记链接：{minutes_url or '暂无'}",
        f"- 飞书文档链接：{doc_url or '待创建'}",
        f"- 纪要文档 Token：`{note.get('note_doc_token') or '暂无'}`",
        f"- 逐字稿文档 Token：`{note.get('verbatim_doc_token') or '暂无'}`",
    ]
    return "\n".join(lines).strip() + "\n"


def create_doc(
    cli_bin: str,
    env: dict[str, str],
    title: str,
    markdown: str,
    folder_token: str | None,
) -> dict[str, Any]:
    args = [
        "docs",
        "+create",
        "--as",
        "user",
        "--title",
        title,
        "--markdown",
        markdown,
    ]
    if folder_token:
        args.extend(["--folder-token", folder_token])
    payload = run_cli(cli_bin, env, args)
    data = unwrap_payload(payload)
    if not isinstance(data, dict):
        raise CLIError("unexpected docs +create payload", code="invalid_doc_payload")
    return data


def partial_result(
    job_id: str,
    title: str,
    minute_token: str,
    minutes_url: str,
    markdown: str,
    transcript_file: str,
    transcript_text: str,
    error: CLIError,
) -> dict[str, Any]:
    return {
        "job_id": job_id,
        "status": "partial",
        "title": title,
        "minute_token": minute_token,
        "minutes_url": minutes_url,
        "markdown": markdown,
        "feishu_doc_url": "",
        "feishu_doc_token": "",
        "transcript_file": transcript_file,
        "transcript_excerpt": transcript_excerpt(transcript_text),
        "error_code": error.code,
        "error_message": str(error),
    }


def failed_result(job_id: str, minute_token: str, error: CLIError) -> dict[str, Any]:
    return {
        "job_id": job_id,
        "status": "failed",
        "title": "",
        "minute_token": minute_token,
        "minutes_url": "",
        "markdown": "",
        "feishu_doc_url": "",
        "feishu_doc_token": "",
        "transcript_file": "",
        "transcript_excerpt": "",
        "error_code": error.code,
        "error_message": str(error),
    }


def main() -> int:
    args = parse_args()
    env = build_env(args.config_dir)
    base_output_dir = Path(args.output_dir).resolve()
    job_dir = base_output_dir / args.job_id
    job_dir.mkdir(parents=True, exist_ok=True)

    try:
        ensure_auth(args.lark_cli_bin, env)
        minute = minute_info(args.lark_cli_bin, env, args.minute_token)
        note = notes_info(args.lark_cli_bin, env, args.minute_token, job_dir)
    except CLIError as exc:
        json.dump(
            failed_result(args.job_id, args.minute_token, exc),
            sys.stdout,
            ensure_ascii=False,
            indent=2,
        )
        sys.stdout.write("\n")
        return 0

    title = args.title or str(minute.get("title") or "会议纪要")
    minutes_url = str(minute.get("url") or "")
    artifacts = note.get("artifacts") or {}
    transcript_file = ""
    if isinstance(artifacts, dict):
        transcript_file = str(artifacts.get("transcript_file") or "")
    transcript_text = read_text(transcript_file)

    markdown = build_markdown(
        title=title,
        minute_token=args.minute_token,
        minutes_url=minutes_url,
        note=note,
        transcript_text=transcript_text,
    )

    try:
        doc = create_doc(
            args.lark_cli_bin,
            env,
            title,
            markdown,
            args.folder_token,
        )
    except CLIError as exc:
        json.dump(
            partial_result(
                job_id=args.job_id,
                title=title,
                minute_token=args.minute_token,
                minutes_url=minutes_url,
                markdown=markdown,
                transcript_file=transcript_file,
                transcript_text=transcript_text,
                error=exc,
            ),
            sys.stdout,
            ensure_ascii=False,
            indent=2,
        )
        sys.stdout.write("\n")
        return 0

    doc_url = str(doc.get("doc_url") or "")
    doc_token = str(doc.get("doc_id") or "")
    final_markdown = build_markdown(
        title=title,
        minute_token=args.minute_token,
        minutes_url=minutes_url,
        note=note,
        transcript_text=transcript_text,
        doc_url=doc_url,
    )
    result = {
        "job_id": args.job_id,
        "status": "succeeded",
        "title": title,
        "minute_token": args.minute_token,
        "minutes_url": minutes_url,
        "markdown": final_markdown,
        "feishu_doc_url": doc_url,
        "feishu_doc_token": doc_token,
        "transcript_file": transcript_file,
        "transcript_excerpt": transcript_excerpt(transcript_text),
        "error_code": "",
        "error_message": "",
    }
    json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
    sys.stdout.write("\n")
    return 0


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