"""langgraph checkpoint setup (multi-chapter chain workflow)

Revision ID: d3e4f5a6b7c8
Revises: c2b3d4e5f6a7
Create Date: 2026-06-23 10:00:00.000000

多章工作流链（docs/design/chain-workflow.md §4/§7）唯一新增 DDL：langgraph
检查点表（`checkpoints` / `checkpoint_blobs` / `checkpoint_writes` /
`checkpoint_migrations`）。这些表**不在** ``Base.metadata``（由 langgraph 自己
管理 schema），故迁移直接调用 langgraph 的一次性 ``setup()`` 建表，而非 ORM
``op.create_table``。env.py 的 ``include_object`` 过滤这些表，避免 ``alembic
check`` 误报漂移（要 drop 这些"非 metadata"表）。

实现说明（CLAUDE.md「LangGraph」+ 设计 §11 回退）：
- DDL 只在 **migrations/CI** 跑（绝不在 app-runtime）——本迁移即该入口。
- ``upgrade()`` 跑在 alembic 的**同步**迁移上下文里（``connection.run_sync``
  下已有 event loop 在跑 async 迁移），故**不能**调 async ``setup_checkpointer``
  /``asyncio.run``（会嵌套 loop 报错）。改用 langgraph 同步 ``PostgresSaver``
  （psycopg），用一条从 ``database_url`` 派生的 psycopg conn string 自建连接，
  与 alembic 的 asyncpg 连接解耦。``setup()`` 幂等（langgraph 内部 migration 版本表）。
- **关键**：langgraph ``setup()`` 跑 ``CREATE INDEX CONCURRENTLY``，它不能在
  事务块内执行，且会等 alembic 那条仍开着事务（持 ``alembic_version`` 锁）的连接
  → 死锁（§11 风险落地）。故用 ``op.get_context().autocommit_block()`` 把 alembic
  的事务先提交、退出事务块，再让 langgraph 在独立 autocommit 连接里建表/建索引。

jobs 表：C2 判定零迁移复用（用 ``result.awaiting_chapter`` 表达待裁决，
``status`` 复用现有枚举），故本迁移不动 jobs。
"""

from __future__ import annotations

from collections.abc import Sequence

revision: str = "d3e4f5a6b7c8"
down_revision: str | None = "c2b3d4e5f6a7"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None

# langgraph 检查点表（langgraph.checkpoint.postgres.base 的 MIGRATIONS 建的全部表）
_CHECKPOINT_TABLES: tuple[str, ...] = (
    "checkpoint_writes",
    "checkpoint_blobs",
    "checkpoints",
    "checkpoint_migrations",
)


def _psycopg_conn_string() -> str:
    """从 settings.database_url 派生 psycopg（同步）conn string。

    settings 用 asyncpg URL（``postgresql+asyncpg://…``）；langgraph 的同步
    ``PostgresSaver`` 走 psycopg，需去掉 SQLAlchemy 的 ``+asyncpg`` 驱动后缀。
    """
    from ww_config import get_settings

    return get_settings().database_url.replace("+asyncpg", "")


def upgrade() -> None:
    from alembic import op
    from langgraph.checkpoint.postgres import PostgresSaver

    # 退出 alembic 事务块（提交 alembic_version 更新，释放其锁），langgraph 的
    # CREATE INDEX CONCURRENTLY 才能在独立连接里跑而不死锁。
    with op.get_context().autocommit_block():
        with PostgresSaver.from_conn_string(_psycopg_conn_string()) as saver:
            saver.setup()


def downgrade() -> None:
    import psycopg

    with psycopg.connect(_psycopg_conn_string()) as conn:
        conn.execute("DROP TABLE IF EXISTS " + ", ".join(_CHECKPOINT_TABLES) + " CASCADE")
        conn.commit()
