"""Back up one SQLite database without overwriting a destination. Python 3.10+."""
import argparse
from contextlib import closing
import os
from pathlib import Path
import sqlite3
import sys
import tempfile


def backup_database(source, destination):
    source = Path(source).resolve(strict=True)
    destination = Path(destination).absolute()
    if not source.is_file():
        raise ValueError("source must be a regular file")
    if os.path.lexists(destination):
        raise FileExistsError(f"destination already exists: {destination}")
    if not destination.parent.is_dir():
        raise ValueError("destination parent directory must exist")

    # A sibling temporary directory keeps the hard link on one filesystem.
    with tempfile.TemporaryDirectory(prefix=".sqlite-backup-", dir=destination.parent) as work:
        temporary = Path(work) / "backup.sqlite"
        with closing(sqlite3.connect(source.as_uri() + "?mode=ro", uri=True,
                                     isolation_level=None)) as src:
            with closing(sqlite3.connect(temporary, isolation_level=None)) as dst:
                src.backup(dst, pages=256, sleep=0.1)
                # Make the result a standalone file even when the source uses WAL.
                mode = dst.execute("PRAGMA journal_mode=DELETE").fetchone()[0]
                if mode.lower() != "delete":
                    raise ValueError("could not select DELETE journal mode")
                checks = dst.execute("PRAGMA quick_check").fetchall()
                if checks != [("ok",)]:
                    raise ValueError(f"quick_check failed: {checks}")
        os.chmod(temporary, 0o600)
        # Unlike replace(), link() fails if another process created the destination.
        os.link(temporary, destination)
    return destination


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("source", type=Path)
    parser.add_argument("destination", type=Path)
    args = parser.parse_args()
    try:
        destination = backup_database(args.source, args.destination)
    except (OSError, ValueError, sqlite3.Error) as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1
    except KeyboardInterrupt:
        print("INTERRUPTED", file=sys.stderr)
        return 130
    print(f"BACKUP OK: {destination}")
    return 0


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