import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { ArchiveDb } from '../../../../../src/modules/netaclaw/runtime/weixin_db/archive_db.js';
import type { ArchiveMessage } from '../../../../../src/modules/netaclaw/runtime/weixin_db/archive_types.js';

const tempDirs: string[] = [];
function tmpDataDir(): string {
  const d = fs.mkdtempSync(path.join(os.tmpdir(), 'archivedb-'));
  tempDirs.push(d);
  return d;
}
afterAll(() => {
  for (const d of tempDirs) {
    try { fs.rmSync(d, { recursive: true, force: true }); } catch { /* ignore */ }
  }
});

function row(over: Partial<ArchiveMessage> = {}): ArchiveMessage {
  return {
    channelId: 6,
    roomId: 'gA@chatroom',
    roomName: '产品研发群',
    msgTable: 'Msg_abc',
    localId: 1,
    serverId: '999',
    localType: 1,
    senderWxid: 'wxid_a',
    senderRemark: null,
    senderNickName: null,
    senderDisplayName: null,
    createTime: 1700000000,
    content: 'hello',
    rawHex: null,
    ...over,
  };
}

describe('ArchiveDb', () => {
  it('archivePath returns <dataDir>/weixin-archive/cid-<id>.db', () => {
    expect(ArchiveDb.archivePath(7, '/data').replace(/\\/g, '/'))
      .toBe('/data/weixin-archive/cid-7.db');
  });

  it('openOrCreate creates file + tables', () => {
    const dir = tmpDataDir();
    const db = ArchiveDb.openOrCreate(1, dir);
    try {
      expect(fs.existsSync(path.join(dir, 'weixin-archive', 'cid-1.db'))).toBe(true);
      expect(db.countMessages('any-room')).toBe(0);
      expect(db.getSyncState('any-room')).toBeNull();
    } finally {
      db.close();
    }
  });

  it('insertMessages returns number of new rows (INSERT OR IGNORE)', () => {
    const db = ArchiveDb.openOrCreate(1, tmpDataDir());
    try {
      const first = db.insertMessages([row({ localId: 1 }), row({ localId: 2 })]);
      expect(first).toBe(2);
      const second = db.insertMessages([row({ localId: 1 }), row({ localId: 3 })]);
      expect(second).toBe(1);
      expect(db.countMessages('gA@chatroom')).toBe(3);
    } finally {
      db.close();
    }
  });

  it('updateSyncState writes and reads back', () => {
    const db = ArchiveDb.openOrCreate(1, tmpDataDir());
    try {
      db.updateSyncState('gA@chatroom', {
        roomName: '产品研发群',
        lastSyncTs: 1700000100,
        lastSyncCount: 12,
        totalCount: 100,
        lastSyncAt: 1700000200,
        lastError: null,
      });
      const s = db.getSyncState('gA@chatroom');
      expect(s).not.toBeNull();
      expect(s!.lastSyncTs).toBe(1700000100);
      expect(s!.lastSyncCount).toBe(12);
      expect(s!.totalCount).toBe(100);
      expect(s!.lastError).toBeNull();
    } finally {
      db.close();
    }
  });

  it('updateSyncState merges partial patches (UPSERT semantics)', () => {
    const db = ArchiveDb.openOrCreate(1, tmpDataDir());
    try {
      db.updateSyncState('r', {
        roomName: 'R',
        lastSyncTs: 100,
        lastSyncCount: 5,
        totalCount: 5,
        lastSyncAt: 200,
        lastError: 'oops',
      });
      db.updateSyncState('r', { lastSyncAt: 300, lastError: null });
      const s = db.getSyncState('r')!;
      expect(s.lastSyncAt).toBe(300);
      expect(s.lastError).toBeNull();
      expect(s.lastSyncTs).toBe(100);
      expect(s.lastSyncCount).toBe(5);
    } finally {
      db.close();
    }
  });

  it('insertMessages stores rawHex when content is null', () => {
    const db = ArchiveDb.openOrCreate(1, tmpDataDir());
    try {
      db.insertMessages([row({ localId: 9, content: null, rawHex: '28b52ffd' })]);
      const handle = (db as any).db as import('better-sqlite3').Database;
      const r = handle.prepare('SELECT content, raw_hex FROM message WHERE local_id=9').get() as any;
      expect(r.content).toBeNull();
      expect(r.raw_hex).toBe('28b52ffd');
    } finally {
      db.close();
    }
  });

  it('stores sender contact identity columns', () => {
    const db = ArchiveDb.openOrCreate(1, tmpDataDir());
    try {
      db.insertMessages([row({
        localId: 10,
        senderWxid: 'wxid_alice',
        senderRemark: 'Alice备注',
        senderNickName: 'Alice昵称',
        senderDisplayName: 'Alice备注',
      })]);
      const handle = (db as any).db as import('better-sqlite3').Database;
      const r = handle.prepare(`
        SELECT sender_wxid, sender_remark, sender_nick_name, sender_display_name
        FROM message WHERE local_id=10
      `).get() as any;
      expect(r.sender_wxid).toBe('wxid_alice');
      expect(r.sender_remark).toBe('Alice备注');
      expect(r.sender_nick_name).toBe('Alice昵称');
      expect(r.sender_display_name).toBe('Alice备注');
    } finally {
      db.close();
    }
  });

  it('backfills sender identity for existing archived rows without counting as new', () => {
    const db = ArchiveDb.openOrCreate(1, tmpDataDir());
    try {
      expect(db.insertMessages([row({
        localId: 11,
        senderWxid: 'wxid_alice',
        senderRemark: null,
        senderNickName: null,
        senderDisplayName: null,
      })])).toBe(1);
      expect(db.insertMessages([row({
        localId: 11,
        senderWxid: 'wxid_alice',
        senderRemark: 'Alice备注',
        senderNickName: 'Alice昵称',
        senderDisplayName: 'Alice备注',
      })])).toBe(0);
      const handle = (db as any).db as import('better-sqlite3').Database;
      const r = handle.prepare(`
        SELECT sender_remark, sender_nick_name, sender_display_name
        FROM message WHERE local_id=11
      `).get() as any;
      expect(r.sender_remark).toBe('Alice备注');
      expect(r.sender_nick_name).toBe('Alice昵称');
      expect(r.sender_display_name).toBe('Alice备注');
    } finally {
      db.close();
    }
  });
});
