-- PROJ: mandanten-job-queue (Phase 1+2)
-- Doku-only (siehe README.md in diesem Verzeichnis) — die tatsächliche
-- Ausführung passiert idempotent über
-- internal/storage/processing_jobs.go initProcessingJobsSchema(),
-- aufgerufen aus (*Store).initSchema() (zuletzt in der Kette, da die Tabelle
-- documents(id) FK-referenziert).
--
-- Mandanten-faire Verarbeitungs-Queue für die NACHGELAGERTE Dokument-
-- verarbeitung (OCR-Extraktion, Taxonomie-Autozuordnung, on_upload-Workflows).
-- Vorher lief das synchron im Upload-Request und erzeugte bei Batch-Scans /
-- SFTP-Massenuploads Lastspitzen.
--
--   * Kein Redis: die Queue ist eine ganz normale Postgres-Tabelle. Dokument-
--     INSERT und Job-INSERT laufen in EINER Transaktion
--     (CreateDocumentWithJob) — nie ein Dokument ohne Job, nie ein Job ohne
--     Dokument.
--   * WORM bleibt synchron im Request: Hash, Ablage unter
--     store/<tenant>/<yyyy>/<mm>/<sha256>.<ext> und chmod 0440 passieren VOR
--     dem INSERT. Der Job liest die archivierte Datei nur und schreibt
--     ausschließlich abgeleitete Metadaten.
--   * Dispatch: Round-Robin über die Mandanten (je Runde ein Job pro Mandant),
--     Locking über FOR UPDATE SKIP LOCKED.
--   * Retry: exponentielles Backoff über next_attempt_at (2^retry_count
--     Sekunden). Ab retry_count > max_retries (Default 5) bleibt der Job
--     dauerhaft 'failed' — kein Automatik-Retry mehr.
--   * derive_title: merkt sich, ob der Titel beim Staging nur ein Platzhalter
--     war und aus dem OCR-Text ersetzt werden darf. Ein vom Benutzer bzw. aus
--     dem SFTP-Dateinamen vorgegebener Titel wird nie überschrieben.

CREATE TABLE IF NOT EXISTS processing_jobs (
    id              BIGSERIAL PRIMARY KEY,
    tenant_id       BIGINT NOT NULL,
    document_id     BIGINT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
    status          TEXT NOT NULL DEFAULT 'queued',   -- queued | processing | done | failed
    retry_count     INT NOT NULL DEFAULT 0,
    derive_title    BOOLEAN NOT NULL DEFAULT false,
    error_message   TEXT,
    next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    started_at      TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS derive_title BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT now();
ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS started_at TIMESTAMPTZ;
ALTER TABLE processing_jobs ADD COLUMN IF NOT EXISTS error_message TEXT;

CREATE INDEX IF NOT EXISTS idx_processing_jobs_dispatch
    ON processing_jobs(tenant_id, status, next_attempt_at, created_at);
CREATE INDEX IF NOT EXISTS idx_processing_jobs_document
    ON processing_jobs(document_id);
CREATE INDEX IF NOT EXISTS idx_processing_jobs_status
    ON processing_jobs(status);

-- Anzeige-/Ablaufstatus am Dokument. Default 'done': der komplette Altbestand
-- wurde noch synchron verarbeitet und ist per Definition fertig — bewusst KEIN
-- Backfill-Skript, der Spalten-Default deckt alle Bestandszeilen ab.
ALTER TABLE documents ADD COLUMN IF NOT EXISTS processing_status TEXT NOT NULL DEFAULT 'done';
