"""Sync orchestration: read moodtracker's SQLite file -> idempotent upsert.

Single stream ('moodtracker_entries'), cursor = highest source_id ingested so
far. Each run re-checks a small overlap of already-synced ids (config.OVERLAP_ROWS)
as a cheap safety net, then upserts anything with id > cursor - overlap.
"""
import time

from . import config, store
from .mood_source import connect_source, fetch_entries_since

STREAM_KEY = "moodtracker_entries"


def run_sync(conn, source_db_path=None):
    """One sync pass. Returns a counts dict. Never raises — errors are
    recorded in ingest_runs/sync_state and returned in totals['errors']."""
    source_db_path = source_db_path or config.SOURCE_DB_PATH
    run_id = store.start_run(conn)
    totals = {"entries": 0, "errors": []}
    try:
        last = store.get_last_synced_id(conn, STREAM_KEY)
        since = max(0, last - config.OVERLAP_ROWS)
        source_conn = connect_source(source_db_path)
        try:
            rows = fetch_entries_since(source_conn, since)
        finally:
            source_conn.close()
        n = store.upsert_entries(conn, rows)
        totals["entries"] = n
        max_id = max((r["id"] for r in rows), default=last)
        store.set_sync_state(conn, STREAM_KEY, max(max_id, last))
        store.finish_run(conn, run_id, "ok", entries=n)
    except Exception as e:  # noqa: BLE001 - isolate failures, keep the loop alive
        totals["errors"].append(str(e))
        store.set_sync_state(
            conn, STREAM_KEY, store.get_last_synced_id(conn, STREAM_KEY),
            status="error", error=str(e),
        )
        store.finish_run(conn, run_id, "error", entries=0, error=str(e))
    return totals


def sync_loop(conn, source_db_path=None, interval=None, once=False):
    interval = interval or config.SYNC_INTERVAL_SECONDS
    while True:
        yield run_sync(conn, source_db_path)
        if once:
            return
        time.sleep(interval)
