import { describe, expect, it } from 'bun:test';
import { Database } from 'bun:sqlite';
import { readSyncMeta, writeSyncMeta, hasSyncedBefore } from './sync-meta';

// Moving an account's sync position out of Postgres and into its own emails.db. Both failure modes are
// expensive and neither is loud, so they are pinned here:
//
//   migrate when we should not  ->  a restored older mailbox inherits a newer position and SKIPS mail
//   fail to migrate            ->  an existing account looks brand new and re-downloads everything

const freshDb = () => {
  const db = new Database(':memory:');
  db.exec('CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT)');
  return db;
};

describe('readSyncMeta', () => {
  it('reads what the file already knows', () => {
    const db = freshDb();
    writeSyncMeta(db, { last_sync_at: '2026-01-01T00:00:00Z', 'imap_lastuid:INBOX': '4200' });

    expect(readSyncMeta(db)).toEqual({ last_sync_at: '2026-01-01T00:00:00Z', 'imap_lastuid:INBOX': '4200' });
  });

  it('migrates keys the file is MISSING, even when it already has others', () => {
    // The case that broke a live 6.9 GB mailbox. The older Gmail backfill wrote last_sync_at and the
    // uidvalidity set into SQLite and never the imap_lastuid ones, so the file was non-empty and
    // half-migrated at once. All-or-nothing skipped it, leaving no lastuid for any folder — and a
    // missing lastuid refetches that folder from UID 1.
    const db = freshDb();
    writeSyncMeta(db, { last_sync_at: '2026-07-23T00:00:00Z', 'imap_uidvalidity:INBOX': '2' });

    const meta = readSyncMeta(db, {
      last_sync_at: '2026-08-10T00:00:00Z', // newer, but the file's own answer stands
      'imap_uidvalidity:INBOX': '2',
      'imap_lastuid:INBOX': '4200', // absent from the file — this is what must arrive
    });

    expect(meta['imap_lastuid:INBOX']).toBe('4200');
    expect(meta.last_sync_at).toBe('2026-07-23T00:00:00Z');
    // Persisted, so the fallback is not needed next time.
    expect(readSyncMeta(db)['imap_lastuid:INBOX']).toBe('4200');
  });

  it('migrates from Postgres exactly once, when the file has nothing', () => {
    // The account existed before the move. Without this it looks like a first sync and re-downloads
    // the entire mailbox.
    const db = freshDb();
    const pg = { last_sync_at: '2026-01-01T00:00:00Z', 'imap_uidvalidity:INBOX': '99' };

    expect(readSyncMeta(db, pg)).toEqual({ last_sync_at: '2026-01-01T00:00:00Z', 'imap_uidvalidity:INBOX': '99' });
    // Backfilled, so the fallback is never consulted again.
    expect(readSyncMeta(db)).toEqual({ last_sync_at: '2026-01-01T00:00:00Z', 'imap_uidvalidity:INBOX': '99' });
  });

  it('NEVER lets Postgres override a position the file already has', () => {
    // The dangerous direction. Restore an older emails.db onto a server whose Postgres row is newer,
    // and topping up from Postgres would advance past mail this file does not contain — skipped
    // permanently, because nothing looks below lastuid again. The file wins, always.
    const db = freshDb();
    writeSyncMeta(db, { 'imap_lastuid:INBOX': '100' });

    const meta = readSyncMeta(db, { 'imap_lastuid:INBOX': '9999' });

    // The key the file has is never overwritten — that is what stops a restored older mailbox being
    // advanced past mail it does not contain.
    expect(meta['imap_lastuid:INBOX']).toBe('100');
  });

  it('ignores non-string values rather than stringifying them', () => {
    // The Postgres column is free-form jsonb. An object coerced to "[object Object]" would be a
    // position that parses as a number nowhere and compares as garbage.
    const db = freshDb();
    expect(readSyncMeta(db, { good: 'yes', bad: { nested: true }, alsoBad: 42 } as never)).toEqual({ good: 'yes' });
  });

  it('is empty for a genuinely new account', () => {
    expect(readSyncMeta(freshDb(), {})).toEqual({});
    expect(readSyncMeta(freshDb(), null)).toEqual({});
  });
});

describe('writeSyncMeta', () => {
  it('upserts rather than duplicating, so a checkpoint mid-sync is safe to repeat', () => {
    const db = freshDb();
    writeSyncMeta(db, { 'imap_lastuid:INBOX': '10' });
    writeSyncMeta(db, { 'imap_lastuid:INBOX': '20' });

    expect(readSyncMeta(db)).toEqual({ 'imap_lastuid:INBOX': '20' });
  });

  it('skips null and undefined instead of writing them as text', () => {
    // "null" as a stored lastuid would parse to NaN and compare false against every real UID.
    const db = freshDb();
    writeSyncMeta(db, { a: '1', b: null, c: undefined });
    expect(readSyncMeta(db)).toEqual({ a: '1' });
  });

  it('stores numbers as their text form, since UIDs arrive both ways', () => {
    const db = freshDb();
    writeSyncMeta(db, { 'imap_lastuid:INBOX': 4200 });
    expect(readSyncMeta(db)['imap_lastuid:INBOX']).toBe('4200');
  });
});

describe('hasSyncedBefore', () => {
  it('is false for a new account and true once the file records a sync', () => {
    const db = freshDb();
    expect(hasSyncedBefore(db)).toBe(false);
    writeSyncMeta(db, { last_sync_at: '2026-01-01T00:00:00Z' });
    expect(hasSyncedBefore(db)).toBe(true);
  });

  it('is true for an account that synced before the move but has not migrated yet', () => {
    // The whole point: an unmigrated account must not be treated as new. This is the check that stands
    // between an existing user and an unnecessary full re-sync of their mailbox.
    expect(hasSyncedBefore(freshDb(), { last_sync_at: '2026-01-01T00:00:00Z' })).toBe(true);
  });

  it('is false when Postgres holds a position but no completed sync', () => {
    // Folder UIDs without last_sync_at means a sync started and never finished.
    expect(hasSyncedBefore(freshDb(), { 'imap_uidvalidity:INBOX': '99' })).toBe(false);
  });
});
