-- Schema SQLite (WAL) pentru gateway RAR AUTOPASS.
-- Vezi plan.md sect. 5 + plan-treapta2.md sect. 4.
-- Treapta 2: adauga conturi cu creds RAR durabile, tabele import, atestari.

PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;

-- Conturi ROAAUTO (clientii care folosesc gateway-ul).
CREATE TABLE IF NOT EXISTS accounts (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            TEXT NOT NULL,
    cui             TEXT,
    email           TEXT,                       -- email canonic de contact al firmei (US-001, PRD 5.12); nullable pt. conturi legacy
    active          INTEGER NOT NULL DEFAULT 1,  -- lifecycle cont (3.1); gate „in asteptare" consumat de 3.3
    -- Stare de ciclu de viata explicita (5.5). Superset al lui `active`: mentinem invariantul
    -- active=1 <=> status='active' (vezi accounts.set_status / set_active). Worker gate-uieste pe status.
    -- pending=neactivat · active=operational · blocked=suspendat reversibil · archived=scos din liste,
    -- date read-only · deleted=stergere soft (tombstone; PII/creds + CUI purjate imediat la stergere,
    -- vezi accounts.delete_account — randul ramane doar pentru audit).
    status          TEXT NOT NULL DEFAULT 'active'
                       CHECK (status IN ('pending','active','blocked','archived','deleted')),
    -- Medii RAR per cont (PRD 5.20 US-001). Fiecare mediu = bifa de activare + slot creds.
    -- accounts.rar_creds_enc (legacy env-less) a fost dropata in US-013 (SQLite DROP COLUMN).
    -- medii_disponibile = enabled AND creds prezente (app/rar_env.py). Cont client nou =
    -- Productie on / Testare off (clientii declara real); contul operator se pune manual pe Testare.
    rar_test_enabled INTEGER NOT NULL DEFAULT 0 CHECK (rar_test_enabled IN (0, 1)),
    rar_prod_enabled INTEGER NOT NULL DEFAULT 1 CHECK (rar_prod_enabled IN (0, 1)),
    rar_creds_test_enc TEXT,    -- creds RAR criptate (Fernet) pentru mediul Testare
    rar_creds_prod_enc TEXT,    -- creds RAR criptate (Fernet) pentru mediul Productie
    rar_env_default TEXT NOT NULL DEFAULT 'prod' CHECK (rar_env_default IN ('test', 'prod')),
    -- Comportament implicit la cod prestatie necunoscut/nemapat pe canalul API:
    -- 0 (default, non-distructiv: submission 'needs_mapping', intra in editorul de mapare) sau
    -- 1 (respinge cererea fara enqueue). Override per-cerere via PrezentareRequest.on_unmapped_error.
    on_unmapped_error_default INTEGER NOT NULL DEFAULT 0
                       CHECK (on_unmapped_error_default IN (0, 1)),
    -- Comutator Auto per cont (PRD 5.19 US-001). AL TREILEA `auto_send` din schema (R6):
    -- DISTINCT de operations_mapping.auto_send si operation_text_rules.auto_send, care sunt
    -- PER-OPERATIE. Acesta e PER-CONT si guverneaza DOAR: (a) valoarea implicita a lui
    -- submissions.held la ingestie (held = 0 daca 1, altfel 1 — vezi accounts.held_for_account);
    -- (b) eliberarea in bloc a randurilor tinute la comutarea OFF -> ON. NU e per-operatie si
    -- NU intra in payload/idempotenta/RAR. Default 0 = Auto OFF (randuri noi tinute manual).
    auto_send_enabled INTEGER NOT NULL DEFAULT 0
                       CHECK (auto_send_enabled IN (0, 1)),
    -- Plan de cont (5.17). Tier de baza al contului (admin aloca manual via CLI set-tier).
    -- trial_until: daca != NULL si > now -> effective_tier() intoarce 'pro' (trial Pro activ).
    -- Cont nou primeste tier='free' + trial_until=now+30z via create_account.
    -- Contul implicit id=1 (dev) primeste DEFAULT 'free' + trial_until=NULL (fara trial).
    tier            TEXT NOT NULL DEFAULT 'free'
                       CHECK (tier IN ('free','standard','pro','premium')),
    trial_until     TEXT,                       -- ISO datetime UTC sau NULL; nullable
    -- Planul CERUT de client la signup (separat de `tier`). NU acorda drepturi:
    -- `tier` ramane sursa unica de adevar pentru gate-ul API (require_api_access) si volum.
    -- Folosit la integrarea platilor: client cere plan -> plateste -> admin/webhook urca `tier`
    -- -> API se deblocheaza. NULL = necunoscut (cont creat via CLI / inainte de coloana).
    requested_plan  TEXT
                       CHECK (requested_plan IS NULL OR requested_plan IN ('free','standard','pro','premium')),
    -- Marca temporala a acceptarii Termenilor + politicii de confidentialitate (GDPR, L.142).
    -- Setata la signup (proba de consimtamant). NULL = cont fara flux de consimtamant (CLI/legacy).
    consent_at      TEXT,
    created_at      TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Un CUI = un cont (cand e prezent). NULL ramane distinct nativ in SQLite -> conturi
-- fara CUI (ex. default) se pot crea multiplu. Unicitate la nivel de index (nu check
-- in helper) ca sa nu existe fereastra de coliziune intre doi create_account concurenti.
CREATE UNIQUE INDEX IF NOT EXISTS ux_accounts_cui ON accounts(cui) WHERE cui IS NOT NULL;
-- Cont implicit (id=1): auth API-key (CORE) inca neimplementat, deci ingestiile vin
-- cu account_id NULL. Le atribuim contului default ca FK + UNIQUE(account_id,...) din
-- operations_mapping sa fie valide; cand auth livreaza, account_id real va curge natural.
INSERT OR IGNORE INTO accounts (id, name) VALUES (1, 'default');

-- Chei API per cont (separate de creds RAR). Stocam doar hash-ul.
CREATE TABLE IF NOT EXISTS api_keys (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    account_id  INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
    key_hash    TEXT NOT NULL UNIQUE,
    active      INTEGER NOT NULL DEFAULT 1,
    created_at  TEXT NOT NULL DEFAULT (datetime('now')),
    revoked_at  TEXT
);

-- Mapare operatie service -> codPrestatie RAR (← mapare_prestatii.DBF, T5).
CREATE TABLE IF NOT EXISTS operations_mapping (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    account_id      INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
    cod_op_service  TEXT NOT NULL,
    cod_prestatie   TEXT NOT NULL,
    auto_send       INTEGER NOT NULL DEFAULT 1,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    UNIQUE (account_id, cod_op_service)
);

-- Cache nomenclator RAR {codPrestatie, numePrestatie} (← prestatii_rar.DBF / live).
CREATE TABLE IF NOT EXISTS nomenclator_rar (
    cod_prestatie   TEXT PRIMARY KEY,
    nume_prestatie  TEXT NOT NULL,
    updated_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

-- Coada de prezentari catre RAR. Masina de stari: plan.md sect. 3.
CREATE TABLE IF NOT EXISTS submissions (
    id               INTEGER PRIMARY KEY AUTOINCREMENT,
    idempotency_key  TEXT NOT NULL UNIQUE,
    account_id       INTEGER REFERENCES accounts(id) ON DELETE SET NULL,
    status           TEXT NOT NULL DEFAULT 'queued'
                       CHECK (status IN ('queued','sending','sent','needs_mapping','needs_data','error')),
    payload_json     TEXT NOT NULL,
    -- Mediul RAR tinta al acestei trimiteri (PRD 5.20 US-001). DEFAULT 'test' e doar plasa
    -- pentru randuri net-noi care nu seteaza explicit; fiecare INSERT (API/import/web) seteaza
    -- rar_env explicit. Backfill din AUTOPASS_RAR_ENV global la migrare (NU lasa pe DEFAULT).
    rar_env          TEXT NOT NULL DEFAULT 'test' CHECK (rar_env IN ('test', 'prod')),
    rar_creds_enc    TEXT,                   -- creds RAR criptate (Fernet), sterse dupa primul login reusit
    rar_status_code  INTEGER,
    rar_error        TEXT,
    id_prezentare    INTEGER,                -- data.id intors de RAR la succes
    retry_count      INTEGER NOT NULL DEFAULT 0,
    next_attempt_at  TEXT,                   -- backoff: randul nu se ia inainte de acest moment (T2)
    sending_since    TEXT,                   -- pentru lease/timeout pe randuri 'sending' orfane (T2)
    purge_after      TEXT,                   -- sent + 90z (T16)
    batch_id         INTEGER,               -- import batch (T7; NULL = canal API)
    row_index        INTEGER,               -- rand in batch (T7; NULL = canal API)
    -- Randuri tinute manual (PRD 5.19 US-001). rand `queued AND held=1` = "In asteptare
    -- (manual)": tinut pana la eliberare umana (trimitere manuala per rand/bulk: held 1 -> 0).
    -- Worker-ul (claim_one) ia doar `queued AND held=0`. held NU intra in payload/idempotenta
    -- si NU pleaca la RAR — e pur control de coada (ca import_rows.reviewed). Snapshot la
    -- ingestie din accounts.auto_send_enabled (vezi accounts.held_for_account).
    held             INTEGER NOT NULL DEFAULT 0 CHECK (held IN (0, 1)),
    created_at       TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at       TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_submissions_status ON submissions(status);
CREATE INDEX IF NOT EXISTS idx_submissions_account_status ON submissions(account_id, status);
-- Nota: idx_submissions_batch si idx_submissions_held se creeaza in _migrate, NU aici. `init_db`
-- ruleaza executescript(schema.sql) INAINTE de _migrate; pe un DB existent pre-`held`/`batch_id`
-- coloana inca nu exista in momentul executarii schemei, deci un CREATE INDEX pe ea ar esua
-- (no such column). Indexul partial `idx_submissions_held ON submissions(held) WHERE held=1`
-- (PRD 5.19 US-001) traieste doar in _migrate, ca `idx_submissions_batch`.

-- Mapare coloane fisier -> campuri canonice (retinuta per cont, semnatura coloane).
CREATE TABLE IF NOT EXISTS column_mappings (
    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
    account_id          INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
    signature_coloane   TEXT NOT NULL,      -- hash/lista sortata a coloanelor fisierului
    json_mapare         TEXT NOT NULL,      -- {col_fisier: camp_canonic, ...} JSON
    format_data         TEXT,               -- ex. "DD.MM.YYYY"
    created_at          TEXT NOT NULL DEFAULT (datetime('now')),
    UNIQUE (account_id, signature_coloane)
);

-- Loturi de import (fisiere incarcate).
CREATE TABLE IF NOT EXISTS import_batches (
    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
    account_id          INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
    filename            TEXT NOT NULL,
    status              TEXT NOT NULL DEFAULT 'staging'
                            CHECK (status IN ('staging','committed','error')),
    total               INTEGER NOT NULL DEFAULT 0,
    ok                  INTEGER NOT NULL DEFAULT 0,
    needs_mapping       INTEGER NOT NULL DEFAULT 0,
    needs_data          INTEGER NOT NULL DEFAULT 0,
    needs_review        INTEGER NOT NULL DEFAULT 0,
    already_sent        INTEGER NOT NULL DEFAULT 0,
    duplicate_in_file   INTEGER NOT NULL DEFAULT 0,
    created_at          TEXT NOT NULL DEFAULT (datetime('now')),
    purge_after         TEXT                -- created_at + 90z (T16)
);

-- Randuri din lot de import (PII criptat cu Fernet).
CREATE TABLE IF NOT EXISTS import_rows (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    batch_id        INTEGER NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
    row_index       INTEGER NOT NULL,
    raw_json        TEXT NOT NULL,          -- PII criptat (Fernet, ca submissions)
    override_json   TEXT,                   -- patch CANONIC editat in preview, criptat Fernet (3.6, Approach B); NULL = fara editare
    reviewed        INTEGER NOT NULL DEFAULT 0, -- US-007 (PRD 5.12): 0=neconfirmat, 1=confirmat de operator; NU intra in payload/idempotenta
    resolved_status TEXT NOT NULL DEFAULT 'pending'
                        CHECK (resolved_status IN (
                            'pending','ok','needs_mapping','needs_data',
                            'needs_review','already_sent','duplicate_in_file'
                        )),
    error           TEXT
);

CREATE INDEX IF NOT EXISTS idx_import_rows_batch ON import_rows(batch_id);

-- Log atestare legala (confirmare import batch, L.142/2023).
CREATE TABLE IF NOT EXISTS import_attestations (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    batch_id        INTEGER NOT NULL REFERENCES import_batches(id) ON DELETE CASCADE,
    account_id      INTEGER NOT NULL,
    confirmed_by    TEXT,                   -- email/identifier utilizator
    ts              TEXT NOT NULL DEFAULT (datetime('now')),
    rows_hash       TEXT NOT NULL,          -- sha256 peste valorile rezolvate confirmate
    n_confirmed     INTEGER NOT NULL
);

-- Utilizatori web (email+parola, legati de un cont). Parola stocata doar ca scrypt hash.
CREATE TABLE IF NOT EXISTS users (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    account_id      INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
    email           TEXT NOT NULL UNIQUE COLLATE NOCASE,
    password_hash   TEXT NOT NULL,  -- hex scrypt(salt, parola)
    salt            TEXT NOT NULL,  -- hex secrets.token_bytes(16), per-user
    scrypt_params   TEXT NOT NULL,  -- eticheta versiune parametri: 'n16384_r8_p1'
    email_verified  INTEGER NOT NULL DEFAULT 0,  -- C19: pregatire viitor
    is_admin        INTEGER NOT NULL DEFAULT 0,  -- pregatire 3.3b
    created_at      TEXT NOT NULL DEFAULT (datetime('now'))
);

-- Jurnal de aplicatie la nivel de eveniment (PRD 5.6). Dublu canal: aici (vizibil
-- in tab "Jurnal") + log text rotativ (depanare). `tip` e text liber documentat
-- (lista extensibila, decizie §5) — adaugam tipuri fara migrare. Toate valorile
-- sunt REDACTATE la scriere (app/observ.py via app/security.py): parole/token ->
-- ***REDACTED***, VIN/nr partial. `context_json` = metadate (NU payload PII integral).
CREATE TABLE IF NOT EXISTS app_events (
    id           INTEGER PRIMARY KEY AUTOINCREMENT,
    ts           TEXT NOT NULL DEFAULT (datetime('now')),
    request_id   TEXT,                     -- corelare cu raspunsul clientului (US-002)
    account_id   INTEGER,                  -- NULL = eveniment de sistem (fara cont)
    sursa        TEXT NOT NULL DEFAULT 'api'
                    CHECK (sursa IN ('api','worker')),
    tip          TEXT NOT NULL,            -- ex. api_prezentari, rar_login, submission_repus
    nivel        TEXT NOT NULL DEFAULT 'INFO',
    cod          TEXT,                     -- cod din catalogul de erori (app/errors.py) daca aplica
    mesaj        TEXT,                     -- mesaj scurt redactat
    context_json TEXT,                     -- JSON metadate redactate (submission_id, count, status...)
    purge_after  TEXT                      -- ts + log_retention_days (US-008)
);
CREATE INDEX IF NOT EXISTS idx_app_events_ts ON app_events(ts);
CREATE INDEX IF NOT EXISTS idx_app_events_account ON app_events(account_id, ts);
CREATE INDEX IF NOT EXISTS idx_app_events_tip ON app_events(tip);

-- Reguli automate de mapare pe text (substring, per cont). PRD 5.8 US-001.
-- auto_send DEFAULT 0 (decizie CEO 2026-06-24): substring are blast-radius mai mare
-- decat maparea exacta; o regula noua rezolva codul dar tine randul pentru verificare
-- umana pana cand operatorul activeaza explicit "In coada".
CREATE TABLE IF NOT EXISTS operation_text_rules (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    account_id      INTEGER NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
    pattern         TEXT NOT NULL,
    cod_prestatie   TEXT NOT NULL,
    auto_send       INTEGER NOT NULL DEFAULT 0,
    priority        INTEGER NOT NULL DEFAULT 0,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    UNIQUE (account_id, pattern)
);
CREATE INDEX IF NOT EXISTS idx_text_rules_account ON operation_text_rules(account_id);

-- Sugestii de mapare (strat SILVER, L14-S3 PRD 5.14).
-- Etichete LLM/embedding — bootstrap; citita DOAR de suggest_codes/pending_unmapped,
-- NICIODATA de load_mapping/resolve_prestatii (separare structurala #13).
-- Cheia = denumire normalizata (fara diacritice, uppercase, spatii colapsate).
-- is_nul=1: non-operatie (ITP, discount, nr. inmatriculare) -> suprima (#4), cod NULL.
-- INSERT OR IGNORE la re-seed: nu suprascrie randuri existente (#2).
CREATE TABLE IF NOT EXISTS mapping_suggestions (
    id                   INTEGER PRIMARY KEY AUTOINCREMENT,
    denumire_normalizata TEXT NOT NULL UNIQUE,
    cod_prestatie        TEXT,                   -- NULL cand is_nul=1 (supresie)
    is_nul               INTEGER NOT NULL DEFAULT 0 CHECK (is_nul IN (0, 1)),
    source               TEXT NOT NULL,          -- 'llm', 'embedding', etc. (#5)
    confidence           REAL NOT NULL DEFAULT 0.0 CHECK (confidence >= 0.0 AND confidence <= 1.0),
    created_at           TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at           TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_mapping_suggestions_cod
    ON mapping_suggestions(cod_prestatie) WHERE cod_prestatie IS NOT NULL;

-- Mapari validate de oameni (strat GOLD partajat cross-account, L14-S3 PRD 5.14).
-- Confirmarile umane din ORICE cont contribuie la acest store (#8).
-- cross-account = suggestion-only (pre-completeaza editorul, F-A/#11), NU auto-send.
-- Auto-send DOAR din operations_mapping (GOLD propriu per-cont, Eng-F2).
-- Cheia = denumire_normalizata (NU cod_op_service: spatii de chei diferite, #14).
CREATE TABLE IF NOT EXISTS shared_mappings (
    id                   INTEGER PRIMARY KEY AUTOINCREMENT,
    denumire_normalizata TEXT NOT NULL UNIQUE,
    cod_prestatie        TEXT NOT NULL,          -- cod RAR valid (GOLD = validat de om)
    source               TEXT NOT NULL DEFAULT 'human',  -- 'human', 'human_import' (#5)
    provenance           TEXT,                   -- detalii: cont, email, batch (#5)
    confidence           REAL NOT NULL DEFAULT 1.0,
    confirmations        INTEGER NOT NULL DEFAULT 1,  -- contor confirmari din orice cont
    created_at           TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at           TEXT NOT NULL DEFAULT (datetime('now'))
);

-- Heartbeat worker (un singur rand, id=1). /healthz citeste de aici.
CREATE TABLE IF NOT EXISTS worker_heartbeat (
    id                  INTEGER PRIMARY KEY CHECK (id = 1),
    last_beat           TEXT,
    last_rar_login_ok   TEXT,
    detail              TEXT
);
INSERT OR IGNORE INTO worker_heartbeat (id, last_beat, detail) VALUES (1, NULL, 'never started');
