"""SQLite-backed extraction run state.

State table lives at {LANDING_DIR}/.state.sqlite — derived from LANDING_DIR,
no extra env var required. SQLite is used (not DuckDB) because state tracking
is a transactional workload: single-row inserts and point-lookup updates, not
analytical scans. WAL mode allows concurrent readers while a run is in progress.

Schema is generic: extractor is a free-text name, cursor_value is a TEXT field
that can hold any cursor type (date string, etag, page number, or JSON for
multi-dimensional cursors). The schema should never need changing — add new
extractor names by just using them.
"""

import sqlite3
from pathlib import Path

_CREATE_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS extraction_runs (
    run_id        INTEGER PRIMARY KEY AUTOINCREMENT,
    extractor     TEXT NOT NULL,
    started_at    TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    finished_at   TEXT,
    status        TEXT NOT NULL DEFAULT 'running',
    files_written INTEGER DEFAULT 0,
    files_skipped INTEGER DEFAULT 0,
    bytes_written INTEGER DEFAULT 0,
    cursor_value  TEXT,
    error_message TEXT
)
"""


def open_state_db(landing_dir: str | Path) -> sqlite3.Connection:
    """Open (or create) the state DB at {landing_dir}/.state.sqlite.

    Enables WAL mode so concurrent readers (e.g. monitoring scripts) can query
    the DB while an extraction run is in progress. Caller must close when done.
    """
    assert landing_dir, "landing_dir must not be empty"
    db_path = Path(landing_dir) / ".state.sqlite"
    db_path.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(str(db_path))
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute(_CREATE_TABLE_SQL)
    conn.commit()
    return conn


def start_run(conn: sqlite3.Connection, extractor: str) -> int:
    """Insert a 'running' row for extractor. Returns the new run_id."""
    assert extractor, "extractor name must not be empty"
    cur = conn.execute(
        "INSERT INTO extraction_runs (extractor, status) VALUES (?, 'running')",
        (extractor,),
    )
    conn.commit()
    assert cur.lastrowid is not None, "INSERT did not return a row ID"
    return cur.lastrowid


def end_run(
    conn: sqlite3.Connection,
    run_id: int,
    *,
    status: str,
    files_written: int = 0,
    files_skipped: int = 0,
    bytes_written: int = 0,
    cursor_value: str | None = None,
    error_message: str | None = None,
) -> None:
    """Update the run row to its final state (success or failed)."""
    assert status in ("success", "failed"), f"status must be 'success' or 'failed', got {status!r}"
    assert run_id > 0, f"run_id must be positive, got {run_id}"
    assert files_written >= 0, f"files_written must be non-negative, got {files_written}"
    assert files_skipped >= 0, f"files_skipped must be non-negative, got {files_skipped}"
    assert bytes_written >= 0, f"bytes_written must be non-negative, got {bytes_written}"
    conn.execute(
        """
        UPDATE extraction_runs
        SET finished_at   = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
            status        = ?,
            files_written = ?,
            files_skipped = ?,
            bytes_written = ?,
            cursor_value  = ?,
            error_message = ?
        WHERE run_id = ?
        """,
        (status, files_written, files_skipped, bytes_written, cursor_value, error_message, run_id),
    )
    conn.commit()


def get_last_cursor(conn: sqlite3.Connection, extractor: str) -> str | None:
    """Return the cursor_value from the most recent successful run, or None."""
    row = conn.execute(
        """
        SELECT cursor_value FROM extraction_runs
        WHERE extractor = ? AND status = 'success' AND cursor_value IS NOT NULL
        ORDER BY run_id DESC
        LIMIT 1
        """,
        (extractor,),
    ).fetchone()
    return row["cursor_value"] if row else None


def get_recent_runs(conn: sqlite3.Connection, extractor: str, limit: int = 10) -> list[dict]:
    """Return the most recent runs for an extractor, newest first."""
    assert limit > 0, f"limit must be positive, got {limit}"
    rows = conn.execute(
        """
        SELECT * FROM extraction_runs
        WHERE extractor = ?
        ORDER BY run_id DESC
        LIMIT ?
        """,
        (extractor, limit),
    ).fetchall()
    return [dict(r) for r in rows]
