package shine.db;

import utils.config.AppConfig;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * DatabaseInitializer — создание новой SQLite-БД по схеме SHiNE.
 *
 * В этой версии:
 *  - создаём ТОЛЬКО таблицы/индексы
 *  - в конце вызываем DatabaseTriggersInstaller.createAllTriggers(st)
 *
 * v2 (sessions):
 *  - active_sessions.session_pwd удалён
 *  - active_sessions.session_key хранит публичный ключ сессии целиком одной строкой
 */
public final class DatabaseInitializer {

    public static final String DB_SCHEMA_VERSION_TABLE = "db_schema_version";
    public static final int SCHEMA_VERSION_1 = 1;

    private DatabaseInitializer() {}

    /* ===================== TEXT (msg_type=1) ===================== */

    public static final short TEXT_POST = 10;
    public static final short TEXT_EDIT_POST = 11;
    public static final short TEXT_REPLY = 20;
    public static final short TEXT_EDIT_REPLY = 21;
    public static final short TEXT_REPOST = 30;

    /* ===================== REACTION (msg_type=2) ===================== */

    public static final short REACTION_LIKE = 1;
    public static final short REACTION_UNLIKE = 2;

    /* ===================== CONNECTION (msg_type=3) ===================== */
    // Близкий друг (close friend). Исторически в коде использовалось имя FRIEND.
    public static final short CONNECTION_FRIEND     = 10;
    public static final short CONNECTION_UNFRIEND   = 11;
    public static final short CONNECTION_CLOSE_FRIEND   = CONNECTION_FRIEND;
    public static final short CONNECTION_UNCLOSE_FRIEND = CONNECTION_UNFRIEND;

    public static final short CONNECTION_CONTACT    = 20;
    public static final short CONNECTION_UNCONTACT  = 21;

    public static final short CONNECTION_FOLLOW     = 30;
    public static final short CONNECTION_UNFOLLOW   = 31;

    public static final short CONNECTION_SPOUSE     = 40;
    public static final short CONNECTION_UNSPOUSE   = 41;

    public static final short CONNECTION_PARENT     = 50;
    public static final short CONNECTION_UNPARENT   = 51;

    public static final short CONNECTION_CHILD      = 52;
    public static final short CONNECTION_UNCHILD    = 53;

    public static final short CONNECTION_SIBLING    = 54;
    public static final short CONNECTION_UNSIBLING  = 55;

    public static final short CONNECTION_KNOWN_PERSON    = 60;
    public static final short CONNECTION_UNKNOWN_PERSON  = 61;

    public static final short CONNECTION_SHINE_CONFIRMED   = 70;
    public static final short CONNECTION_SHINE_UNCONFIRMED = 71;

    public static final short CONNECTION_SHINE_SEEN   = 74;
    public static final short CONNECTION_SHINE_UNSEEN = 75;

    public static void createNewDB(String[] args) {
        AppConfig config = AppConfig.getInstance();
        String dbPath = config.getParam("db.path");

        if (dbPath == null || dbPath.isBlank()) {
            System.err.println("Параметр db.path не задан в application.properties");
            return;
        }

        Path dbFile = Paths.get(dbPath);
        try {
            Path parent = dbFile.getParent();
            if (parent != null && !Files.exists(parent)) {
                Files.createDirectories(parent);
            }

            if (Files.exists(dbFile)) {
                System.out.println("Файл базы данных уже существует: " + dbFile.toAbsolutePath());
                System.out.print("Пересоздать БД (СТАРАЯ БУДЕТ УДАЛЕНА)? [y/N]: ");

                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                String answer = reader.readLine();
                if (!"y".equalsIgnoreCase(answer) && !"yes".equalsIgnoreCase(answer)) {
                    System.out.println("Операция отменена. БД не изменена.");
                    return;
                }

                Files.delete(dbFile);
                System.out.println("Старый файл БД удалён.");
            }

            createSchema("jdbc:sqlite:" + dbPath);
            System.out.println("Новая БД успешно создана по пути: " + dbFile.toAbsolutePath());

        } catch (IOException e) {
            System.err.println("Ошибка работы с файлом БД: " + e.getMessage());
        } catch (SQLException e) {
            System.err.println("Ошибка создания схемы БД: " + e.getMessage());
        }
    }

    public static void ensureSchemaV1Structure(String jdbcUrl) throws SQLException {
        createSchema(jdbcUrl, false);
    }

    private static void createSchema(String jdbcUrl) throws SQLException {
        createSchema(jdbcUrl, true);
    }

    private static void createSchema(String jdbcUrl, boolean initializeVersionRow) throws SQLException {
        try {
            Class.forName("org.sqlite.JDBC");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("SQLite JDBC driver not found", e);
        }

        try (Connection conn = DriverManager.getConnection(jdbcUrl);
             Statement st = conn.createStatement()) {

            st.execute("PRAGMA foreign_keys = ON");

            // 1. solana_users
            // ВАЖНО:
            // - Все требуемые поля теперь лежат в solana_users:
            //   login, blockchain_name, solana_key, blockchain_key, device_key
            // - Поиск по login в DAO сделан case-insensitive.
            // - Для защиты от дублей "Anya" и "anya" добавляем COLLATE NOCASE на PRIMARY KEY.
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS solana_users (
                    login           TEXT    NOT NULL PRIMARY KEY COLLATE NOCASE,
                    blockchain_name TEXT    NOT NULL,
                    solana_key      TEXT    NOT NULL,
                    blockchain_key  TEXT    NOT NULL,
                    device_key      TEXT    NOT NULL
                );
                """);

            st.executeUpdate("""
                CREATE UNIQUE INDEX IF NOT EXISTS uq_solana_users_blockchain_name
                ON solana_users (blockchain_name);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_solana_users_login
                ON solana_users (login);
                """);

            // 2. active_sessions (v2)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS active_sessions (
                    session_id                 TEXT    NOT NULL PRIMARY KEY,
                    login                      TEXT    NOT NULL,
                    session_key                TEXT    NOT NULL,
                    storage_pwd                TEXT    NOT NULL,
                    session_created_at_ms      INTEGER NOT NULL,
                    last_authirificated_at_ms  INTEGER NOT NULL,
                    push_endpoint              TEXT,
                    push_p256dh_key            TEXT,
                    push_auth_key              TEXT,
                    client_ip                  TEXT,
                    client_info_from_client    TEXT,
                    client_info_from_request   TEXT,
                    session_type               INTEGER NOT NULL DEFAULT 1,
                    client_platform            TEXT    NOT NULL DEFAULT '',
                    user_language              TEXT,
                    FOREIGN KEY (login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_active_sessions_login
                ON active_sessions (login);
                """);

            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS esp_pairing_settings (
                    login               TEXT    NOT NULL PRIMARY KEY COLLATE NOCASE,
                    enabled             INTEGER NOT NULL DEFAULT 0,
                    password_hash       TEXT    NOT NULL DEFAULT '',
                    ttl_seconds         INTEGER NOT NULL DEFAULT 300,
                    failed_attempts     INTEGER NOT NULL DEFAULT 0,
                    first_failed_at_ms  INTEGER NOT NULL DEFAULT 0,
                    blocked_until_ms    INTEGER NOT NULL DEFAULT 0,
                    updated_at_ms       INTEGER NOT NULL,
                    FOREIGN KEY (login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS esp_pairing_requests (
                    pairing_id                TEXT    NOT NULL PRIMARY KEY,
                    login                     TEXT    NOT NULL,
                    requester_session_key     TEXT    NOT NULL,
                    requester_session_type    INTEGER NOT NULL DEFAULT 1,
                    requester_client_platform TEXT    NOT NULL DEFAULT '',
                    payload_type              INTEGER NOT NULL,
                    status                    TEXT    NOT NULL,
                    short_code                TEXT    NOT NULL,
                    fingerprint_b58           TEXT    NOT NULL,
                    encrypted_payload         TEXT,
                    reject_reason             TEXT,
                    approved_by_session_id    TEXT,
                    created_at_ms             INTEGER NOT NULL,
                    expires_at_ms             INTEGER NOT NULL,
                    updated_at_ms             INTEGER NOT NULL,
                    delivered_to_homeserver   INTEGER NOT NULL DEFAULT 0,
                    FOREIGN KEY (login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_esp_pairing_requests_login_status
                ON esp_pairing_requests (login, status, expires_at_ms);
                """);

            // 3. users_params
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS users_params (
                    login          TEXT    NOT NULL,
                    param          TEXT    NOT NULL,
                    time_ms        INTEGER NOT NULL,
                    value          TEXT    NOT NULL,
                    device_key     TEXT,
                    signature      TEXT,
                    FOREIGN KEY (login) REFERENCES solana_users(login),
                    UNIQUE (login, param)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_users_params_login
                ON users_params (login);
                """);

            // 4. ip_geo_cache
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS ip_geo_cache (
                    ip             TEXT    NOT NULL PRIMARY KEY,
                    geo            TEXT,
                    updated_at_ms  INTEGER NOT NULL
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_ip_geo_cache_updated_at
                ON ip_geo_cache (updated_at_ms);
                """);

            // 5. blockchain_state
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS blockchain_state (
                    blockchain_name   TEXT    NOT NULL PRIMARY KEY,
                    login             TEXT    NOT NULL,
                    blockchain_key    TEXT    NOT NULL,

                    size_limit        INTEGER NOT NULL,
                    file_size_bytes   INTEGER NOT NULL,

                    last_block_number INTEGER NOT NULL,
                    last_block_hash   BLOB,

                    updated_at_ms     INTEGER NOT NULL,

                    FOREIGN KEY (login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_blockchain_state_login
                ON blockchain_state (login);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_blockchain_state_updated_at
                ON blockchain_state (updated_at_ms);
                """);

            // 6. blocks (+ line_code)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS blocks (
                    login                TEXT    NOT NULL,
                    bch_name             TEXT    NOT NULL,
                    block_number         INTEGER NOT NULL CHECK(block_number >= 0),

                    msg_type             INTEGER NOT NULL,
                    msg_sub_type         INTEGER NOT NULL,

                    block_bytes          BLOB    NOT NULL,

                    -- target (reply/like/edit и т.д.)
                    to_login             TEXT,
                    to_bch_name          TEXT,
                    to_block_number      INTEGER CHECK(to_block_number IS NULL OR to_block_number >= 0),
                    to_block_hash        BLOB,

                    -- собственные данные
                    block_hash           BLOB    NOT NULL,
                    block_signature      BLOB    NOT NULL,

                    -- если этот блок был изменён последним edit'ом
                    edited_by_block_number INTEGER CHECK(edited_by_block_number IS NULL OR edited_by_block_number >= 0),

                    -- линейность (опционально)
                    line_code           INTEGER CHECK(line_code IS NULL OR line_code >= 0),
                    prev_line_number    INTEGER CHECK(prev_line_number IS NULL OR prev_line_number >= 0),
                    prev_line_hash      BLOB,
                    this_line_number    INTEGER CHECK(this_line_number IS NULL OR this_line_number >= 0),

                    FOREIGN KEY (login) REFERENCES solana_users(login),
                    FOREIGN KEY (bch_name) REFERENCES blockchain_state(blockchain_name),

                    UNIQUE (bch_name, block_number)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_blocks_by_chain_number
                ON blocks (bch_name, block_number);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_blocks_to_target
                ON blocks (to_login, to_bch_name, to_block_number);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_blocks_by_line
                ON blocks (bch_name, line_code, this_line_number);
                """);

            // 7) connections_state
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS connections_state (
                    login           TEXT    NOT NULL,
                    rel_type        INTEGER NOT NULL,
                    to_login        TEXT    NOT NULL,
                    to_bch_name     TEXT    NOT NULL,
                    to_block_number INTEGER NOT NULL,
                    to_block_hash   BLOB    NOT NULL,

                    FOREIGN KEY (login) REFERENCES solana_users(login),

                    UNIQUE (login, rel_type, to_login, to_bch_name, to_block_number, to_block_hash)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_connections_state_login
                ON connections_state (login);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_connections_state_to_login
                ON connections_state (to_login);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_connections_state_pair
                ON connections_state (login, to_login);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_connections_state_target
                ON connections_state (login, rel_type, to_bch_name, to_block_number);
                """);

            // 8) message_stats
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS message_stats (
                    to_login          TEXT    NOT NULL,
                    to_bch_name       TEXT    NOT NULL,
                    to_block_number   INTEGER NOT NULL,
                    to_block_hash     BLOB    NOT NULL,

                    likes_count       INTEGER NOT NULL DEFAULT 0,
                    replies_count     INTEGER NOT NULL DEFAULT 0,
                    edits_count       INTEGER NOT NULL DEFAULT 0,

                    UNIQUE (
                        to_login,
                        to_bch_name,
                        to_block_number,
                        to_block_hash
                    )
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_message_stats_target
                ON message_stats (to_bch_name, to_block_number, to_block_hash);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_message_stats_login
                ON message_stats (to_login);
                """);

            // 8.0) reactions_state (идемпотентный LIKE/UNLIKE per actor/target)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS reactions_state (
                    from_login       TEXT    NOT NULL,
                    from_bch_name    TEXT    NOT NULL,
                    reaction_type    INTEGER NOT NULL,
                    to_login         TEXT    NOT NULL,
                    to_bch_name      TEXT    NOT NULL,
                    to_block_number  INTEGER NOT NULL,
                    to_block_hash    BLOB    NOT NULL,
                    last_sub_type    INTEGER NOT NULL,

                    UNIQUE (
                        from_login,
                        from_bch_name,
                        reaction_type,
                        to_login,
                        to_bch_name,
                        to_block_number,
                        to_block_hash
                    )
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_reactions_state_target
                ON reactions_state (to_bch_name, to_block_number, to_block_hash);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_reactions_state_actor
                ON reactions_state (from_login, from_bch_name, reaction_type);
                """);

            // 9) channel_names_state (global normalized channel names)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS channel_names_state (
                    slug                      TEXT    NOT NULL,
                    display_name              TEXT    NOT NULL,
                    channel_description       TEXT    NOT NULL DEFAULT '',
                    owner_login               TEXT    NOT NULL,
                    owner_bch_name            TEXT    NOT NULL,
                    channel_type_code         INTEGER NOT NULL DEFAULT 1,
                    channel_type_version      INTEGER NOT NULL DEFAULT 1,
                    channel_root_block_number INTEGER NOT NULL,
                    channel_root_block_hash   BLOB    NOT NULL,
                    created_at_ms             INTEGER NOT NULL
                );
                """);

            st.executeUpdate("""
                CREATE UNIQUE INDEX IF NOT EXISTS uq_channel_names_state_owner_type_slug
                ON channel_names_state (owner_bch_name, channel_type_code, slug);
                """);

            st.executeUpdate("""
                CREATE UNIQUE INDEX IF NOT EXISTS uq_channel_names_state_target
                ON channel_names_state (owner_bch_name, channel_root_block_number, channel_root_block_hash);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_channel_names_state_owner
                ON channel_names_state (owner_login, owner_bch_name);
                """);

            // 9.1) chat200_state
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS chat200_state (
                    owner_login               TEXT    NOT NULL,
                    owner_bch_name            TEXT    NOT NULL,
                    channel_root_block_number INTEGER NOT NULL,
                    channel_root_block_hash   BLOB    NOT NULL,
                    channel_name              TEXT    NOT NULL,
                    channel_type_version      INTEGER NOT NULL,
                    chat_title                TEXT    NOT NULL DEFAULT '',
                    updated_at_ms             INTEGER NOT NULL,
                    PRIMARY KEY (owner_bch_name, channel_root_block_number)
                );
                """);
            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_chat200_state_owner
                ON chat200_state (owner_login, owner_bch_name);
                """);

            // 9.2) chat200_members_state
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS chat200_members_state (
                    owner_bch_name            TEXT    NOT NULL,
                    channel_root_block_number INTEGER NOT NULL,
                    member_login              TEXT    NOT NULL,
                    member_channel_name       TEXT    NOT NULL,
                    is_active                 INTEGER NOT NULL,
                    updated_at_ms             INTEGER NOT NULL,
                    updated_by_block_number   INTEGER NOT NULL,
                    PRIMARY KEY (
                        owner_bch_name,
                        channel_root_block_number,
                        member_login,
                        member_channel_name
                    )
                );
                """);
            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_chat200_members_owner
                ON chat200_members_state (owner_bch_name, channel_root_block_number, is_active);
                """);

            // 10) direct_messages
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS direct_messages (
                    message_id     TEXT    NOT NULL PRIMARY KEY,
                    from_login     TEXT    NOT NULL,
                    to_login       TEXT    NOT NULL,
                    text           TEXT    NOT NULL,
                    created_at_ms  INTEGER NOT NULL,
                    FOREIGN KEY (from_login) REFERENCES solana_users(login),
                    FOREIGN KEY (to_login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_direct_messages_to_login
                ON direct_messages (to_login, created_at_ms);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_direct_messages_from_login
                ON direct_messages (from_login, created_at_ms);
                """);

            // 11) user_push_tokens
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS user_push_tokens (
                    token_id       TEXT    NOT NULL PRIMARY KEY,
                    login          TEXT    NOT NULL,
                    session_id     TEXT    NOT NULL,
                    provider       TEXT    NOT NULL,
                    token          TEXT    NOT NULL,
                    platform       TEXT,
                    user_agent     TEXT,
                    updated_at_ms  INTEGER NOT NULL,
                    FOREIGN KEY (login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_user_push_tokens_login
                ON user_push_tokens (login);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_user_push_tokens_login_session
                ON user_push_tokens (login, session_id);
                """);

            // 11) signed_direct_message_replay (anti-replay window)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS signed_direct_message_replay (
                    from_login     TEXT    NOT NULL,
                    time_ms        INTEGER NOT NULL,
                    nonce          INTEGER NOT NULL,
                    created_at_ms  INTEGER NOT NULL,
                    UNIQUE (from_login, time_ms, nonce)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_signed_dm_replay_created
                ON signed_direct_message_replay (created_at_ms);
                """);

            // 12) signed_direct_messages_history (сырой бинарный пакет + мета)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS signed_direct_messages_history (
                    message_id         TEXT    NOT NULL PRIMARY KEY,
                    from_login         TEXT    NOT NULL,
                    to_login           TEXT    NOT NULL,
                    target_mode        INTEGER NOT NULL,
                    target_session_id  TEXT,
                    message_type       INTEGER NOT NULL,
                    time_ms            INTEGER NOT NULL,
                    nonce              INTEGER NOT NULL,
                    raw_packet         BLOB    NOT NULL,
                    created_at_ms      INTEGER NOT NULL,
                    FOREIGN KEY (from_login) REFERENCES solana_users(login),
                    FOREIGN KEY (to_login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_signed_dm_history_to
                ON signed_direct_messages_history (to_login, created_at_ms);
                """);

            // 13) signed_messages_v2 (универсальное хранилище блоков типов 1/2/3/4)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS signed_messages_v2 (
                    message_key            TEXT    NOT NULL PRIMARY KEY,
                    base_key               TEXT    NOT NULL,
                    target_login           TEXT    NOT NULL,
                    from_login             TEXT    NOT NULL,
                    to_login               TEXT    NOT NULL,
                    time_ms                INTEGER NOT NULL,
                    nonce                  INTEGER NOT NULL,
                    message_type           INTEGER NOT NULL,
                    raw_block              BLOB    NOT NULL,
                    created_at_ms          INTEGER NOT NULL,
                    source_api             TEXT    NOT NULL,
                    origin_session_id      TEXT,
                    receipt_ref_base_key   TEXT,
                    receipt_ref_type       INTEGER,
                    FOREIGN KEY (from_login) REFERENCES solana_users(login),
                    FOREIGN KEY (to_login) REFERENCES solana_users(login)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_signed_messages_v2_target
                ON signed_messages_v2 (target_login, time_ms, created_at_ms);
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_signed_messages_v2_base
                ON signed_messages_v2 (base_key, message_type);
                """);

            st.executeUpdate("""
                CREATE UNIQUE INDEX IF NOT EXISTS uq_signed_messages_v2_receipt_incoming
                ON signed_messages_v2 (target_login, receipt_ref_base_key)
                WHERE message_type = 3 AND receipt_ref_base_key IS NOT NULL;
                """);

            st.executeUpdate("""
                CREATE UNIQUE INDEX IF NOT EXISTS uq_signed_messages_v2_receipt_outgoing
                ON signed_messages_v2 (target_login, receipt_ref_base_key)
                WHERE message_type = 4 AND receipt_ref_base_key IS NOT NULL;
                """);

            // 14) signed_message_session_delivery (доставка по сессиям)
            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS signed_message_session_delivery (
                    message_key       TEXT    NOT NULL,
                    session_id        TEXT    NOT NULL,
                    delivered         INTEGER NOT NULL DEFAULT 0,
                    delivered_at_ms   INTEGER,
                    created_at_ms     INTEGER NOT NULL,
                    PRIMARY KEY (message_key, session_id),
                    FOREIGN KEY (message_key) REFERENCES signed_messages_v2(message_key)
                );
                """);

            st.executeUpdate("""
                CREATE INDEX IF NOT EXISTS idx_signed_message_delivery_session
                ON signed_message_session_delivery (session_id, delivered);
                """);

            st.executeUpdate("""
                CREATE TABLE IF NOT EXISTS db_schema_version (
                    id             INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
                    schema_version INTEGER NOT NULL,
                    updated_at_ms  INTEGER NOT NULL
                );
                """);

            if (initializeVersionRow) {
                st.executeUpdate("""
                    INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
                    VALUES (1, 1, CAST(strftime('%s','now') AS INTEGER) * 1000)
                    ON CONFLICT(id) DO UPDATE SET
                        schema_version = excluded.schema_version,
                        updated_at_ms = excluded.updated_at_ms;
                    """);
            }

            DatabaseTriggersInstaller.createAllTriggers(st);
        }
    }
}
