import assert from 'node:assert/strict';
import { mkdtempSync } from 'node:fs';
import { mkdir, readFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { test } from 'node:test';

import {
  listMaildirFolders,
  maildirFolderPath,
  maildirHomePath,
  readMaildirMessage,
  scanMaildirMailbox,
  setMaildirMessageSeen,
  sqliteMaildirStorageKey,
  writeMaildirMessage
} from '../src/maildir-store.js';

test('Maildir store writes original bytes atomically and exposes folder flags', async () => {
  const root = mkdtempSync(path.join(tmpdir(), 'mailhub-maildir-store-'));
  const address = 'User@Example.com';
  const rawMessageBytes = Buffer.from([
    ...Buffer.from('From: sender@example.net\r\nSubject: Latin1\r\n\r\n', 'ascii'),
    0x48, 0xe9, 0x6c, 0x6c, 0x6f, 0x0d, 0x0a
  ]);
  const storage = await writeMaildirMessage({
    root,
    address,
    rawMessageBytes,
    folder: '归档/2026',
    flags: ['\\Flagged'],
    keywords: ['$Label1'],
    read: false,
    receivedAt: '2026-07-16T12:00:00.000Z',
    storageKey: 'mhdb-42'
  });

  assert.equal(storage.backend, 'maildir');
  assert.equal(storage.key, 'mhdb-42');
  assert.match(storage.relpath, /^mail\/\./);
  assert.match(storage.relpath, /\/cur\//);
  assert.equal(storage.size, rawMessageBytes.length);
  assert.equal(maildirHomePath(root, address), path.join(root, 'user@example.com'));
  assert.match(maildirFolderPath(root, address, '归档/2026'), /mail\/\.&/);

  const [entry] = await scanMaildirMailbox({ root, address });
  assert.equal(entry.storageKey, 'mhdb-42');
  assert.equal(entry.folder, '归档/2026');
  assert.deepEqual(entry.flags, ['\\Flagged']);
  assert.deepEqual(entry.keywords, ['$Label1']);
  assert.equal(entry.read, false);
  const stored = await readMaildirMessage(entry);
  assert.deepEqual(stored.bytes, rawMessageBytes);
  assert.deepEqual(await readFile(entry.filePath), rawMessageBytes);

  const updated = await setMaildirMessageSeen({
    root,
    address,
    storageKey: storage.key,
    relpath: storage.relpath,
    seen: true
  });
  assert.match(updated.relpath, /\/cur\//);
  assert.match(updated.relpath, /:2,FSa$/);
  const [seenEntry] = await scanMaildirMailbox({ root, address });
  assert.deepEqual(seenEntry.flags, ['\\Flagged', '\\Seen']);
  assert.deepEqual(seenEntry.keywords, ['$Label1']);
  assert.equal(seenEntry.read, true);
});

test('Maildir store rejects unsafe addresses and never overwrites a storage key', async () => {
  const root = mkdtempSync(path.join(tmpdir(), 'mailhub-maildir-safety-'));
  assert.throws(() => maildirHomePath(root, '../escape@example.com'), /地址不正确/);
  assert.equal(sqliteMaildirStorageKey(123), 'mhdb-123');
  assert.throws(() => sqliteMaildirStorageKey(0), /ID 不正确/);

  const first = Buffer.from('Subject: first\r\n\r\nfirst');
  const storage = await writeMaildirMessage({
    root,
    address: 'safe@example.com',
    rawMessageBytes: first,
    storageKey: 'mhdb-1'
  });
  assert.match(storage.relpath, /\/new\//);
  assert.doesNotMatch(storage.relpath, /:2,/);
  await assert.rejects(
    writeMaildirMessage({
      root,
      address: 'safe@example.com',
      rawMessageBytes: Buffer.from('Subject: second\r\n\r\nsecond'),
      storageKey: 'mhdb-1'
    }),
    /存储标识冲突/
  );
  const [entry] = await scanMaildirMailbox({ root, address: 'safe@example.com' });
  assert.deepEqual((await readMaildirMessage(entry)).bytes, first);
});

test('Maildir keyword letters never become uppercase system flags', async () => {
  const root = mkdtempSync(path.join(tmpdir(), 'mailhub-maildir-keywords-'));
  const keywords = ['$Label1', '$Label2', '$Label3', '$Label4'];
  await writeMaildirMessage({
    root,
    address: 'keywords@example.com',
    rawMessageBytes: Buffer.from('Subject: keyword flags\r\n\r\nBody'),
    keywords,
    storageKey: 'mhdb-7'
  });

  const [entry] = await scanMaildirMailbox({ root, address: 'keywords@example.com' });
  assert.deepEqual(entry.flags, []);
  assert.deepEqual(entry.keywords, keywords);
});

test('Maildir folder encoding keeps literal dots distinct from hierarchy separators', async () => {
  const root = mkdtempSync(path.join(tmpdir(), 'mailhub-maildir-folder-dots-'));
  const address = 'folders@example.com';
  const dottedPath = maildirFolderPath(root, address, 'team.ops');
  const nestedPath = maildirFolderPath(root, address, 'team/ops');
  assert.notEqual(dottedPath, nestedPath);
  assert.match(dottedPath, /\^2e/);
  assert.notEqual(
    maildirFolderPath(root, address, 'team^2eops'),
    dottedPath
  );
  assert.match(maildirFolderPath(root, address, '~archive'), /\.\^7earchive$/);

  await writeMaildirMessage({
    root,
    address,
    folder: 'team.ops',
    rawMessageBytes: Buffer.from('Subject: dotted\r\n\r\nDotted'),
    storageKey: 'mhdb-8'
  });
  await writeMaildirMessage({
    root,
    address,
    folder: 'team/ops',
    rawMessageBytes: Buffer.from('Subject: nested\r\n\r\nNested'),
    storageKey: 'mhdb-9'
  });

  const entries = await scanMaildirMailbox({ root, address });
  assert.deepEqual(entries.map((entry) => entry.folder).sort(), ['team.ops', 'team/ops']);
});

test('Maildir folder inventory ignores Dovecot internal and incomplete dot directories', async () => {
  const root = mkdtempSync(path.join(tmpdir(), 'mailhub-maildir-folder-inventory-'));
  const address = 'inventory@example.com';
  await writeMaildirMessage({
    root,
    address,
    folder: 'Projects',
    rawMessageBytes: Buffer.from('Subject: project\r\n\r\nBody'),
    storageKey: 'mhdb-10'
  });

  const mailRoot = path.join(maildirHomePath(root, address), 'mail');
  await mkdir(path.join(mailRoot, '.dovecot-internal'), { recursive: true });
  await mkdir(path.join(mailRoot, '.incomplete', 'cur'), { recursive: true });

  assert.deepEqual(
    (await listMaildirFolders({ root, address })).sort(),
    ['INBOX', 'Projects']
  );
});
