"""
Export serving tables from lakehouse.duckdb to serving.duckdb (atomic swap).

Called by the supervisor after each SQLMesh transform run. Reads all tables in
the 'serving' schema from the pipeline DB (DUCKDB_PATH / lakehouse.duckdb),
writes them to a temp file, then atomically renames it to the serving DB path
(SERVING_DUCKDB_PATH / serving.duckdb).

The web app's _get_conn() detects the inode change on the next query and
reopens the connection automatically — no restart or signal required.

Usage:
    DUCKDB_PATH=lakehouse.duckdb SERVING_DUCKDB_PATH=serving.duckdb \
        uv run beanflows pipeline run export_serving
"""
import logging
import os

import duckdb

logger = logging.getLogger(__name__)


def export_serving() -> None:
    """Copy all serving.* tables from the pipeline DB to the serving DB atomically."""
    pipeline_path = os.getenv("DUCKDB_PATH", "")
    serving_path = os.getenv("SERVING_DUCKDB_PATH", "")
    assert pipeline_path, "DUCKDB_PATH must be set"
    assert serving_path, "SERVING_DUCKDB_PATH must be set"
    assert os.path.exists(pipeline_path), f"Pipeline DB not found: {pipeline_path}"

    # Temp path must not start with "serving" — DuckDB names the catalog after
    # the filename stem, so "serving.duckdb.tmp" → catalog "serving", which
    # clashes with the schema we create inside it.
    tmp_path = os.path.join(os.path.dirname(os.path.abspath(serving_path)), "_export.duckdb")

    src = duckdb.connect(pipeline_path, read_only=True)
    try:
        tables = src.sql(
            "SELECT table_name FROM information_schema.tables"
            " WHERE table_schema = 'serving' ORDER BY table_name"
        ).fetchall()
        assert tables, f"No tables found in serving schema of {pipeline_path}"
        logger.info(f"Exporting {len(tables)} serving tables: {[t[0] for t in tables]}")

        dst = duckdb.connect(tmp_path)
        try:
            dst.execute("CREATE SCHEMA IF NOT EXISTS serving")
            for (table,) in tables:
                # Read via Arrow so there is no cross-connection catalog ambiguity.
                arrow_data = src.sql(f"SELECT * FROM serving.{table}").arrow()
                dst.register("_src", arrow_data)
                dst.execute(f"CREATE OR REPLACE TABLE serving.{table} AS SELECT * FROM _src")
                dst.unregister("_src")
                row_count = dst.sql(f"SELECT count(*) FROM serving.{table}").fetchone()[0]
                logger.info(f"  serving.{table}: {row_count:,} rows")
        finally:
            dst.close()
    finally:
        src.close()

    # Atomic rename — on Linux, rename() is atomic when src and dst are on the same filesystem.
    os.rename(tmp_path, serving_path)
    logger.info(f"Serving DB atomically updated: {serving_path}")
