import fs from 'node:fs';
import path from 'node:path';
import pg from 'pg';

export async function createDb({ databaseUrl, dataDir } = {}) {
  if (databaseUrl) {
    const db = new PgDb(databaseUrl);
    await db.init();
    console.log(`[NAOMI gateway] БД: PostgreSQL (${db.hostPort})`);
    return db;
  }
  const db = new SqliteDb(dataDir);
  await db.init();
  console.log('[NAOMI gateway] БД: SQLite (задайте DATABASE_URL для PostgreSQL)');
  return db;
}

export const SCHEMA = {
  keys: `
    CREATE TABLE IF NOT EXISTS keys (
      id BIGSERIAL PRIMARY KEY,
      key TEXT UNIQUE NOT NULL,
      label TEXT NOT NULL DEFAULT '',
      balance_milli BIGINT NOT NULL DEFAULT 0,
      blocked BOOLEAN NOT NULL DEFAULT FALSE,
      created_at BIGINT NOT NULL
    )`,
  usage: `
    CREATE TABLE IF NOT EXISTS usage (
      id BIGSERIAL PRIMARY KEY,
      key_id BIGINT NOT NULL,
      model TEXT NOT NULL DEFAULT '',
      prompt_tokens BIGINT NOT NULL DEFAULT 0,
      completion_tokens BIGINT NOT NULL DEFAULT 0,
      cost_milli BIGINT NOT NULL DEFAULT 0,
      created_at BIGINT NOT NULL
    )`,
  index: 'CREATE INDEX IF NOT EXISTS idx_usage_key ON usage(key_id, created_at)',
  topups: `
    CREATE TABLE IF NOT EXISTS topups (
      id BIGSERIAL PRIMARY KEY,
      key_id BIGINT NOT NULL,
      amount_milli BIGINT NOT NULL,
      method TEXT NOT NULL DEFAULT 'manual',
      status TEXT NOT NULL DEFAULT 'pending',
      provider_id TEXT NOT NULL DEFAULT '',
      created_at BIGINT NOT NULL,
      processed_at BIGINT
    )`,
  usageDaily: `
    CREATE TABLE IF NOT EXISTS usage_daily (
      id BIGSERIAL PRIMARY KEY,
      key_id BIGINT NOT NULL,
      day BIGINT NOT NULL,
      requests BIGINT NOT NULL DEFAULT 0,
      prompt_tokens BIGINT NOT NULL DEFAULT 0,
      completion_tokens BIGINT NOT NULL DEFAULT 0,
      cost_milli BIGINT NOT NULL DEFAULT 0
    )`,
  usageDailyIndex: 'CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_daily ON usage_daily(key_id, day)',
  accounts: `
    CREATE TABLE IF NOT EXISTS accounts (
      id BIGSERIAL PRIMARY KEY,
      email TEXT UNIQUE NOT NULL,
      phone TEXT NOT NULL DEFAULT '',
      password_hash TEXT NOT NULL DEFAULT '',
      google_sub TEXT NOT NULL DEFAULT '',
      yandex_sub TEXT NOT NULL DEFAULT '',
      vk_sub TEXT NOT NULL DEFAULT '',
      key_id BIGINT NOT NULL,
      created_at BIGINT NOT NULL
    )`,
};

const ACCOUNT_ALTERS = [
  'ALTER TABLE accounts ADD COLUMN phone TEXT NOT NULL DEFAULT \'\'',
  'ALTER TABLE accounts ADD COLUMN yandex_sub TEXT NOT NULL DEFAULT \'\'',
  'ALTER TABLE accounts ADD COLUMN vk_sub TEXT NOT NULL DEFAULT \'\'',
];

const sqliteSchema = {
  keys: SCHEMA.keys
    .replace('BIGSERIAL PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT')
    .replace('BOOLEAN NOT NULL DEFAULT FALSE', 'INTEGER NOT NULL DEFAULT 0'),
  usage: SCHEMA.usage.replace('BIGSERIAL PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT'),
};

export class PgDb {
  constructor(connectionString) {
    pg.types.setTypeParser(20, (v) => parseInt(v, 10));
    this.pool = new pg.Pool({ connectionString, max: 20, idleTimeoutMillis: 30000 });
    const url = new URL(connectionString.replace('postgres://', 'postgresql://').replace(/postgresql:\/\/([^@]+)@/, 'postgresql://$1@'));
    this.hostPort = `${url.hostname}:${url.port || 5432}${url.pathname}`;
  }

  async init() {
    await this.pool.query(SCHEMA.keys);
    await this.pool.query(SCHEMA.usage);
    await this.pool.query(SCHEMA.index);
    await this.pool.query(SCHEMA.topups);
    await this.pool.query(SCHEMA.accounts);
    for (const sql of ACCOUNT_ALTERS) await this.pool.query(sql).catch(() => {});
    await this.pool.query(SCHEMA.usageDaily).catch(() => {});
    await this.pool.query(SCHEMA.usageDailyIndex).catch(() => {});
  }

  async rollupUsage(keepDays = 7) {
    const cutoff = Date.now() - keepDays * 86400_000;
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      await client.query(`
        INSERT INTO usage_daily (key_id, day, requests, prompt_tokens, completion_tokens, cost_milli)
        SELECT key_id, created_at / 86400000, COUNT(*), SUM(prompt_tokens), SUM(completion_tokens), SUM(cost_milli)
        FROM usage WHERE created_at < $1
        GROUP BY key_id, created_at / 86400000
        ON CONFLICT (key_id, day) DO UPDATE SET
          requests = usage_daily.requests + EXCLUDED.requests,
          prompt_tokens = usage_daily.prompt_tokens + EXCLUDED.prompt_tokens,
          completion_tokens = usage_daily.completion_tokens + EXCLUDED.completion_tokens,
          cost_milli = usage_daily.cost_milli + EXCLUDED.cost_milli`, [cutoff]);
      const { rowCount } = await client.query('DELETE FROM usage WHERE created_at < $1', [cutoff]);
      await client.query('COMMIT');
      return rowCount ?? 0;
    } catch (err) {
      await client.query('ROLLBACK');
      throw err;
    } finally {
      client.release();
    }
  }

  mapKey(row) {
    return { ...row, blocked: !!row.blocked };
  }

  async createKey({ key, label, balanceMilli }) {
    const { rows } = await this.pool.query(
      'INSERT INTO keys (key, label, balance_milli, blocked, created_at) VALUES ($1, $2, $3, FALSE, $4) RETURNING *',
      [key, label ?? '', Math.round(balanceMilli ?? 0), Date.now()],
    );
    return this.mapKey(rows[0]);
  }

  async getKey(key) {
    const { rows } = await this.pool.query('SELECT * FROM keys WHERE key = $1', [key]);
    return rows[0] ? this.mapKey(rows[0]) : undefined;
  }

  async getKeyById(id) {
    const { rows } = await this.pool.query('SELECT * FROM keys WHERE id = $1', [id]);
    return rows[0] ? this.mapKey(rows[0]) : undefined;
  }

  async listKeys() {
    const { rows } = await this.pool.query('SELECT id, key, label, balance_milli, blocked, created_at FROM keys ORDER BY id DESC');
    return rows.map((r) => this.mapKey(r));
  }

  async topUp(id, milli) {
    await this.pool.query('UPDATE keys SET balance_milli = balance_milli + $1 WHERE id = $2', [Math.round(milli), id]);
  }

  async setBlocked(id, blocked) {
    await this.pool.query('UPDATE keys SET blocked = $1 WHERE id = $2', [!!blocked, id]);
  }

  async setLabel(id, label) {
    await this.pool.query('UPDATE keys SET label = $1 WHERE id = $2', [label, id]);
  }

  async charge(keyId, { model, promptTokens, completionTokens, costMilli }) {
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      await client.query(
        'INSERT INTO usage (key_id, model, prompt_tokens, completion_tokens, cost_milli, created_at) VALUES ($1, $2, $3, $4, $5, $6)',
        [keyId, model ?? '', promptTokens ?? 0, completionTokens ?? 0, Math.round(costMilli), Date.now()],
      );
      await client.query('UPDATE keys SET balance_milli = balance_milli - $1 WHERE id = $2', [Math.round(costMilli), keyId]);
      await client.query('COMMIT');
    } catch (err) {
      await client.query('ROLLBACK');
      throw err;
    } finally {
      client.release();
    }
  }

  async usageByKey(keyId, sinceMs = 0) {
    const { rows } = await this.pool.query(`
      SELECT
        (SELECT COUNT(*) FROM usage WHERE key_id = $1 AND created_at > $2)::int
        + (SELECT COALESCE(SUM(requests), 0) FROM usage_daily WHERE key_id = $1 AND day >= $2 / 86400000)::int AS requests,
        (SELECT COALESCE(SUM(prompt_tokens), 0) FROM usage WHERE key_id = $1 AND created_at > $2)::int
        + (SELECT COALESCE(SUM(prompt_tokens), 0) FROM usage_daily WHERE key_id = $1 AND day >= $2 / 86400000)::int AS prompt_tokens,
        (SELECT COALESCE(SUM(completion_tokens), 0) FROM usage WHERE key_id = $1 AND created_at > $2)::int
        + (SELECT COALESCE(SUM(completion_tokens), 0) FROM usage_daily WHERE key_id = $1 AND day >= $2 / 86400000)::int AS completion_tokens,
        (SELECT COALESCE(SUM(cost_milli), 0) FROM usage WHERE key_id = $1 AND created_at > $2)::int
        + (SELECT COALESCE(SUM(cost_milli), 0) FROM usage_daily WHERE key_id = $1 AND day >= $2 / 86400000)::int AS cost_milli`, [keyId, sinceMs]);
    return rows[0];
  }

  async usageTotal(sinceMs = 0) {
    const { rows } = await this.pool.query(`
      SELECT
        (SELECT COUNT(*) FROM usage WHERE created_at > $1)::int
        + (SELECT COALESCE(SUM(requests), 0) FROM usage_daily WHERE day >= $1 / 86400000)::int AS requests,
        (SELECT COALESCE(SUM(prompt_tokens), 0) FROM usage WHERE created_at > $1)::int
        + (SELECT COALESCE(SUM(prompt_tokens), 0) FROM usage_daily WHERE day >= $1 / 86400000)::int AS prompt_tokens,
        (SELECT COALESCE(SUM(completion_tokens), 0) FROM usage WHERE created_at > $1)::int
        + (SELECT COALESCE(SUM(completion_tokens), 0) FROM usage_daily WHERE day >= $1 / 86400000)::int AS completion_tokens,
        (SELECT COALESCE(SUM(cost_milli), 0) FROM usage WHERE created_at > $1)::int
        + (SELECT COALESCE(SUM(cost_milli), 0) FROM usage_daily WHERE day >= $1 / 86400000)::int AS cost_milli`, [sinceMs]);
    return rows[0];
  }

  async createTopup({ keyId, amountMilli, method = 'manual', providerId = '' }) {
    const { rows } = await this.pool.query(
      'INSERT INTO topups (key_id, amount_milli, method, status, provider_id, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id',
      [keyId, Math.round(amountMilli), method, providerId ? 'pending' : 'pending', providerId, Date.now()],
    );
    return rows[0].id;
  }

  async setTopupProvider(id, providerId) {
    await this.pool.query('UPDATE topups SET provider_id = $1 WHERE id = $2', [providerId, id]);
  }

  async getTopup(id) {
    const { rows } = await this.pool.query('SELECT * FROM topups WHERE id = $1', [id]);
    return rows[0];
  }

  async listTopups(status) {
    const { rows } = status
      ? await this.pool.query('SELECT t.id, t.key_id, t.amount_milli, t.method, t.status, t.provider_id, t.created_at, k.key, k.label FROM topups t JOIN keys k ON k.id = t.key_id WHERE t.status = $1 ORDER BY t.id DESC LIMIT 100', [status])
      : await this.pool.query('SELECT t.id, t.key_id, t.amount_milli, t.method, t.status, t.provider_id, t.created_at, k.key, k.label FROM topups t JOIN keys k ON k.id = t.key_id ORDER BY t.id DESC LIMIT 100');
    return rows;
  }

  async approveTopup(id) {
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      const { rows } = await client.query('SELECT * FROM topups WHERE id = $1 AND status = $2 FOR UPDATE', [id, 'pending']);
      if (!rows[0]) { await client.query('COMMIT'); return null; }
      await client.query('UPDATE keys SET balance_milli = balance_milli + $1 WHERE id = $2', [rows[0].amount_milli, rows[0].key_id]);
      await client.query('UPDATE topups SET status = $1, processed_at = $2 WHERE id = $3', ['paid', Date.now(), id]);
      await client.query('COMMIT');
      return rows[0];
    } catch (err) {
      await client.query('ROLLBACK');
      throw err;
    } finally {
      client.release();
    }
  }

  async rejectTopup(id) {
    const { rowCount } = await this.pool.query("UPDATE topups SET status = 'rejected', processed_at = $1 WHERE id = $2 AND status = 'pending'", [Date.now(), id]);
    return rowCount > 0;
  }

  async createAccount({ email, phone, passwordHash, googleSub, yandexSub, vkSub, keyId }) {
    const { rows } = await this.pool.query(
      'INSERT INTO accounts (email, phone, password_hash, google_sub, yandex_sub, vk_sub, key_id, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *',
      [email ?? '', phone ?? '', passwordHash ?? '', googleSub ?? '', yandexSub ?? '', vkSub ?? '', keyId, Date.now()],
    );
    return rows[0];
  }

  async getAccountByEmail(email) {
    const { rows } = await this.pool.query('SELECT * FROM accounts WHERE email = $1', [email]);
    return rows[0];
  }

  async getAccountByPhone(phone) {
    const { rows } = await this.pool.query("SELECT * FROM accounts WHERE phone = $1 AND phone <> ''", [phone]);
    return rows[0];
  }

  async getAccountByLogin(login) {
    const { rows } = await this.pool.query('SELECT * FROM accounts WHERE email = $1 OR (phone = $1 AND phone <> \'\')', [login]);
    return rows[0];
  }

  async getAccountByGoogleSub(sub) {
    const { rows } = await this.pool.query("SELECT * FROM accounts WHERE google_sub = $1 AND google_sub <> ''", [sub]);
    return rows[0];
  }

  async getAccountBySub(provider, sub) {
    const col = { google: 'google_sub', yandex: 'yandex_sub', vk: 'vk_sub' }[provider];
    if (!col) return undefined;
    const { rows } = await this.pool.query(`SELECT * FROM accounts WHERE ${col} = $1 AND ${col} <> ''`, [sub]);
    return rows[0];
  }

  async setAccountSub(accountId, provider, sub) {
    const col = { google: 'google_sub', yandex: 'yandex_sub', vk: 'vk_sub' }[provider];
    if (!col) return;
    await this.pool.query(`UPDATE accounts SET ${col} = $1 WHERE id = $2`, [sub, accountId]);
  }

  async setAccountKey(accountId, keyId) {
    await this.pool.query('UPDATE accounts SET key_id = $1 WHERE id = $2', [keyId, accountId]);
  }

  async deleteKeyRow(keyId) {
    await this.pool.query('DELETE FROM usage WHERE key_id = $1', [keyId]);
    await this.pool.query('DELETE FROM topups WHERE key_id = $1', [keyId]);
    await this.pool.query('DELETE FROM keys WHERE id = $1', [keyId]);
  }
}

export class SqliteDb {
  constructor(dataDir) {
    if (!dataDir) throw new Error('SqliteDb: нужен dataDir');
    fs.mkdirSync(dataDir, { recursive: true });
    this.dataDir = dataDir;
  }

  async init() {
    const { DatabaseSync } = await import('node:sqlite');
    this.db = new DatabaseSync(path.join(this.dataDir, 'naomi.db'));
    this.db.exec('PRAGMA journal_mode = WAL;');
    this.db.exec(`${sqliteSchema.keys}; ${sqliteSchema.usage}; ${SCHEMA.index}; ${SCHEMA.topups.replace('BIGSERIAL PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT')}; ${SCHEMA.accounts.replace('BIGSERIAL PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT')};`);
    for (const sql of ACCOUNT_ALTERS) { try { this.db.exec(sql); } catch {} }
    this.db.exec(SCHEMA.usageDaily.replace('BIGSERIAL PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT'));
    this.db.exec(SCHEMA.usageDailyIndex);
    this.st = {
      insertKey: this.db.prepare('INSERT INTO keys (key, label, balance_milli, blocked, created_at) VALUES (?, ?, ?, 0, ?)'),
      getKey: this.db.prepare('SELECT * FROM keys WHERE key = ?'),
      listKeys: this.db.prepare('SELECT id, key, label, balance_milli, blocked, created_at FROM keys ORDER BY id DESC'),
      topUp: this.db.prepare('UPDATE keys SET balance_milli = balance_milli + ? WHERE id = ?'),
      setBlocked: this.db.prepare('UPDATE keys SET blocked = ? WHERE id = ?'),
      setLabel: this.db.prepare('UPDATE keys SET label = ? WHERE id = ?'),
      insertUsage: this.db.prepare('INSERT INTO usage (key_id, model, prompt_tokens, completion_tokens, cost_milli, created_at) VALUES (?, ?, ?, ?, ?, ?)'),
      charge: this.db.prepare('UPDATE keys SET balance_milli = balance_milli - ? WHERE id = ?'),
      usageByKey: this.db.prepare('SELECT COUNT(*) as requests, SUM(prompt_tokens) as prompt_tokens, SUM(completion_tokens) as completion_tokens, SUM(cost_milli) as cost_milli FROM usage WHERE key_id = ? AND created_at > ?'),
      usageTotal: this.db.prepare('SELECT COUNT(*) as requests, SUM(prompt_tokens) as prompt_tokens, SUM(completion_tokens) as completion_tokens, SUM(cost_milli) as cost_milli FROM usage WHERE created_at > ?'),
      insertTopup: this.db.prepare('INSERT INTO topups (key_id, amount_milli, method, status, provider_id, created_at) VALUES (?, ?, ?, ?, ?, ?)'),
      getTopup: this.db.prepare('SELECT * FROM topups WHERE id = ?'),
      listTopupsAll: this.db.prepare('SELECT t.id, t.key_id, t.amount_milli, t.method, t.status, t.provider_id, t.created_at, k.key, k.label FROM topups t JOIN keys k ON k.id = t.key_id ORDER BY t.id DESC LIMIT 100'),
      listTopupsStatus: this.db.prepare('SELECT t.id, t.key_id, t.amount_milli, t.method, t.status, t.provider_id, t.created_at, k.key, k.label FROM topups t JOIN keys k ON k.id = t.key_id WHERE t.status = ? ORDER BY t.id DESC LIMIT 100'),
      approveTopup: this.db.prepare("UPDATE topups SET status = 'paid', processed_at = ? WHERE id = ? AND status = 'pending'"),
      rejectTopup: this.db.prepare("UPDATE topups SET status = 'rejected', processed_at = ? WHERE id = ? AND status = 'pending'"),
      insertAccount: this.db.prepare('INSERT INTO accounts (email, password_hash, google_sub, key_id, created_at) VALUES (?, ?, ?, ?, ?)'),
      accountByEmail: this.db.prepare('SELECT * FROM accounts WHERE email = ?'),
      accountByGoogleSub: this.db.prepare('SELECT * FROM accounts WHERE google_sub = ?'),
      setAccountKey: this.db.prepare('UPDATE accounts SET key_id = ? WHERE id = ?'),
      deleteUsageByKey: this.db.prepare('DELETE FROM usage WHERE key_id = ?'),
      deleteTopupsByKey: this.db.prepare('DELETE FROM topups WHERE key_id = ?'),
      deleteKeyRow: this.db.prepare('DELETE FROM keys WHERE id = ?'),
    };
  }

  async createKey({ key, label, balanceMilli }) {
    this.st.insertKey.run(key, label ?? '', Math.round(balanceMilli ?? 0), Date.now());
    return this.getKey(key);
  }

  async getKey(key) {
    const row = this.st.getKey.get(key);
    return row ? { ...row, blocked: !!row.blocked } : undefined;
  }

  async getKeyById(id) {
    const row = this.db.prepare('SELECT * FROM keys WHERE id = ?').get(id);
    return row ? { ...row, blocked: !!row.blocked } : undefined;
  }

  async listKeys() {
    return this.st.listKeys.all().map((r) => ({ ...r, blocked: !!r.blocked }));
  }

  async topUp(id, milli) {
    this.st.topUp.run(Math.round(milli), id);
  }

  async setBlocked(id, blocked) {
    this.st.setBlocked.run(blocked ? 1 : 0, id);
  }

  async setLabel(id, label) {
    this.st.setLabel.run(label, id);
  }

  async charge(keyId, { model, promptTokens, completionTokens, costMilli }) {
    this.st.insertUsage.run(keyId, model ?? '', promptTokens ?? 0, completionTokens ?? 0, Math.round(costMilli), Date.now());
    this.st.charge.run(Math.round(costMilli), keyId);
  }

  async rollupUsage(keepDays = 7) {
    const cutoff = Date.now() - keepDays * 86400_000;
    this.db.exec('BEGIN');
    try {
      this.db.prepare(`
        INSERT INTO usage_daily (key_id, day, requests, prompt_tokens, completion_tokens, cost_milli)
        SELECT key_id, created_at / 86400000, COUNT(*), SUM(prompt_tokens), SUM(completion_tokens), SUM(cost_milli)
        FROM usage WHERE created_at < ?
        GROUP BY key_id, created_at / 86400000
        ON CONFLICT (key_id, day) DO UPDATE SET
          requests = requests + excluded.requests,
          prompt_tokens = prompt_tokens + excluded.prompt_tokens,
          completion_tokens = completion_tokens + excluded.completion_tokens,
          cost_milli = cost_milli + excluded.cost_milli`).run(cutoff);
      const info = this.db.prepare('DELETE FROM usage WHERE created_at < ?').run(cutoff);
      this.db.exec('COMMIT');
      return info.changes;
    } catch (err) {
      this.db.exec('ROLLBACK');
      throw err;
    }
  }

  async usageByKey(keyId, sinceMs = 0) {
    const row = this.st.usageByKey.get(keyId, sinceMs);
    return { ...row, cost_milli: row.cost_milli ?? 0 };
  }

  async usageTotal(sinceMs = 0) {
    const row = this.st.usageTotal.get(sinceMs);
    return { ...row, cost_milli: row.cost_milli ?? 0 };
  }

  async createTopup({ keyId, amountMilli, method = 'manual', providerId = '' }) {
    const info = this.st.insertTopup.run(keyId, Math.round(amountMilli), method, 'pending', providerId, Date.now());
    return Number(info.lastInsertRowid);
  }

  async setTopupProvider(id, providerId) {
    this.db.prepare('UPDATE topups SET provider_id = ? WHERE id = ?').run(providerId, id);
  }

  async getTopup(id) {
    return this.st.getTopup.get(id);
  }

  async listTopups(status) {
    return status ? this.st.listTopupsStatus.all(status) : this.st.listTopupsAll.all();
  }

  async approveTopup(id) {
    const t = this.st.getTopup.get(id);
    if (!t || t.status !== 'pending') return null;
    this.st.topUp(t.key_id, t.amount_milli);
    this.st.approveTopup.run(Date.now(), id);
    return t;
  }

  async rejectTopup(id) {
    const info = this.st.rejectTopup.run(Date.now(), id);
    return info.changes > 0;
  }

  async createAccount({ email, phone, passwordHash, googleSub, yandexSub, vkSub, keyId }) {
    this.db.prepare('INSERT INTO accounts (email, phone, password_hash, google_sub, yandex_sub, vk_sub, key_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)')
      .run(email ?? '', phone ?? '', passwordHash ?? '', googleSub ?? '', yandexSub ?? '', vkSub ?? '', keyId, Date.now());
    return this.st.accountByEmail.get(email);
  }

  async getAccountByEmail(email) {
    return this.st.accountByEmail.get(email);
  }

  async getAccountByPhone(phone) {
    return this.db.prepare("SELECT * FROM accounts WHERE phone = ? AND phone <> ''").get(phone);
  }

  async getAccountByLogin(login) {
    return this.db.prepare("SELECT * FROM accounts WHERE email = ? OR (phone = ? AND phone <> '')").get(login, login);
  }

  async getAccountByGoogleSub(sub) {
    return this.st.accountByGoogleSub.get(sub);
  }

  async getAccountBySub(provider, sub) {
    const col = { google: 'google_sub', yandex: 'yandex_sub', vk: 'vk_sub' }[provider];
    if (!col) return undefined;
    return this.db.prepare(`SELECT * FROM accounts WHERE ${col} = ? AND ${col} <> ''`).get(sub);
  }

  async setAccountSub(accountId, provider, sub) {
    const col = { google: 'google_sub', yandex: 'yandex_sub', vk: 'vk_sub' }[provider];
    if (!col) return;
    this.db.prepare(`UPDATE accounts SET ${col} = ? WHERE id = ?`).run(sub, accountId);
  }

  async setAccountKey(accountId, keyId) {
    this.st.setAccountKey.run(keyId, accountId);
  }

  async deleteKeyRow(keyId) {
    this.st.deleteUsageByKey.run(keyId);
    this.st.deleteTopupsByKey.run(keyId);
    this.st.deleteKeyRow.run(keyId);
  }
}
