"""PG 连接 + Session factory。

`ZCBOT_DB_URL` 必填,标准 SQLAlchemy URL,如:
    postgresql+psycopg://user:pass@host:5432/zcbot

未设置时 get_engine() 抛 RuntimeError 并打印指引(不引导 docker)。
users 行由 web auth 入口按需 INSERT (`web.auth.ensure_user_row`),引擎层不再 bootstrap。
"""
from __future__ import annotations

import os
from contextlib import contextmanager
from typing import Iterator, Optional

from sqlalchemy import Engine, create_engine
from sqlalchemy.orm import Session, sessionmaker

_engine: Optional[Engine] = None
_SessionLocal: Optional[sessionmaker[Session]] = None


_DB_URL_HINT = (
    "ZCBOT_DB_URL is not set.\n"
    "  export ZCBOT_DB_URL='postgresql+psycopg://user:pass@host:5432/dbname'\n"
    "  (local: dev/staging PG; SaaS: production PG)"
)


def _read_db_url() -> str:
    url = os.environ.get("ZCBOT_DB_URL", "").strip()
    if not url:
        raise RuntimeError(_DB_URL_HINT)
    return url


def get_engine() -> Engine:
    """单例 engine。线程安全(SQLAlchemy 内置 pool)。"""
    global _engine, _SessionLocal
    if _engine is None:
        url = _read_db_url()
        _engine = create_engine(url, pool_pre_ping=True, future=True)
        _SessionLocal = sessionmaker(bind=_engine, expire_on_commit=False, future=True)
    return _engine


def get_sessionmaker() -> sessionmaker[Session]:
    if _SessionLocal is None:
        get_engine()
    assert _SessionLocal is not None
    return _SessionLocal


@contextmanager
def session_scope() -> Iterator[Session]:
    """事务上下文:成功 commit,异常 rollback,总是 close。"""
    sm = get_sessionmaker()
    s = sm()
    try:
        yield s
        s.commit()
    except Exception:
        s.rollback()
        raise
    finally:
        s.close()
