"""Durable outbox — a SQLite-backed FIFO queue of results pending upload.

Every parsed result is enqueued here first, then a single uploader worker drains
it to the cloud. This guarantees no result is lost if the internet drops: items
stay 'pending' and are retried until they succeed.
"""

from __future__ import annotations

import json
import logging
import sqlite3
import threading
import time
from typing import Any, Dict, List, Tuple

log = logging.getLogger("buffer")


class Outbox:
    """Thread-safe persistent queue of outbound result payloads."""

    def __init__(self, db_path: str) -> None:
        # check_same_thread=False + a lock: one connection shared across threads.
        self._conn = sqlite3.connect(db_path, check_same_thread=False)
        self._lock = threading.Lock()
        self._conn.execute(
            """
            CREATE TABLE IF NOT EXISTS outbox (
                id           INTEGER PRIMARY KEY AUTOINCREMENT,
                machine_id   INTEGER NOT NULL,
                payload      TEXT NOT NULL,
                attempts     INTEGER NOT NULL DEFAULT 0,
                last_error   TEXT,
                created_at   TEXT NOT NULL,
                status       TEXT NOT NULL DEFAULT 'pending'
            )
            """
        )
        self._conn.commit()

    def enqueue(self, machine_id: int, payload: Dict[str, Any]) -> None:
        with self._lock:
            self._conn.execute(
                "INSERT INTO outbox (machine_id, payload, created_at) VALUES (?, ?, ?)",
                (machine_id, json.dumps(payload), time.strftime("%Y-%m-%d %H:%M:%S")),
            )
            self._conn.commit()

    def pending(self, limit: int) -> List[Tuple[int, Dict[str, Any]]]:
        with self._lock:
            cur = self._conn.execute(
                "SELECT id, payload FROM outbox WHERE status = 'pending' "
                "ORDER BY id ASC LIMIT ?",
                (limit,),
            )
            return [(row[0], json.loads(row[1])) for row in cur.fetchall()]

    def mark_sent(self, row_id: int) -> None:
        with self._lock:
            self._conn.execute("DELETE FROM outbox WHERE id = ?", (row_id,))
            self._conn.commit()

    def mark_failed(self, row_id: int, error: str) -> None:
        with self._lock:
            self._conn.execute(
                "UPDATE outbox SET attempts = attempts + 1, last_error = ? WHERE id = ?",
                (error[:500], row_id),
            )
            self._conn.commit()

    def count_pending(self) -> int:
        with self._lock:
            cur = self._conn.execute(
                "SELECT COUNT(*) FROM outbox WHERE status = 'pending'"
            )
            return int(cur.fetchone()[0])
