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

import {
  authenticateApiToken,
  createApiToken,
  createDomain,
  createInboundMailbox,
  createInboundMessage,
  createUser,
  getAdminResourceInventory,
  getApiToken,
  getInboundMessage,
  initDatabase,
  listApiTokens,
  listInboundMailboxFolders,
  listInboundMailboxes,
  revokeApiToken,
  rotateApiToken,
  searchInboundMessages,
  updateUser
} from '../src/db.js';

test('API tokens are recoverable only for explicit owner views and rotate without weakening authentication', () => {
  const database = initDatabase(tempDataDir(), 'api-token-recovery-secret');
  const user = createUser({
    username: 'token-owner',
    email: 'token-owner@example.com',
    password: 'password123'
  });
  const expiresAt = new Date(Date.now() + 60 * 60 * 1000).toISOString();
  const created = createApiToken(user.id, 'Receive worker', {
    scopes: ['send', 'messages:read'],
    expiresAt,
    mailboxAccess: 'owner'
  });

  assert.match(created.token, /^mh_/);
  assert.equal(created.tokenRecoverable, true);
  assert.equal(created.mailboxAccess, 'owner');
  assert.deepEqual(created.mailboxIds, []);

  const defaultListItem = listApiTokens(user.id)[0];
  assert.equal('token' in defaultListItem, false);
  assert.equal(defaultListItem.tokenRecoverable, true);
  assert.equal(listApiTokens(user.id, { includeToken: true })[0].token, created.token);
  assert.equal('token' in authenticateApiToken(created.token).token, false);
  assert.equal('token' in getAdminResourceInventory().users[0].apiTokens[0], false);

  authenticateApiToken(created.token);
  assert.ok(getApiToken(created.id, user.id).lastUsedAt);

  database.prepare('UPDATE api_tokens SET token_secret = ? WHERE id = ?').run('', created.id);
  const historical = getApiToken(created.id, user.id, { includeToken: true });
  assert.equal(historical.tokenRecoverable, false);
  assert.equal('token' in historical, false);
  assert.equal(authenticateApiToken(created.token).user.id, user.id);

  const rotated = rotateApiToken(created.id, user.id);
  assert.match(rotated.token, /^mh_/);
  assert.notEqual(rotated.token, created.token);
  assert.deepEqual(rotated.scopes, ['send', 'messages:read']);
  assert.equal(rotated.mailboxAccess, 'owner');
  assert.equal(rotated.expiresAt, expiresAt);
  assert.equal(rotated.lastUsedAt, null);
  assert.equal(authenticateApiToken(created.token), null);
  assert.equal(authenticateApiToken(rotated.token).user.id, user.id);

  assert.equal(revokeApiToken(created.id, user.id), true);
  assert.throws(() => rotateApiToken(created.id, user.id), /已撤销/);
});

test('API token mailbox scopes validate ownership and keep message queries fail closed', () => {
  const database = initDatabase(tempDataDir(), 'api-token-mailbox-secret');
  const admin = createUser({
    username: 'scope-admin',
    email: 'scope-admin@example.com',
    password: 'password123',
    role: 'admin'
  });
  const alice = createUser({
    username: 'scope-alice',
    email: 'scope-alice@example.com',
    password: 'password123'
  });
  const bob = createUser({
    username: 'scope-bob',
    email: 'scope-bob@example.com',
    password: 'password123'
  });
  createDomain(alice.id, domainFixture('scope-alice.example'));
  createDomain(bob.id, domainFixture('scope-bob.example'));
  const aliceMailbox = createInboundMailbox(alice.id, { address: 'inbox@scope-alice.example' });
  const bobMailbox = createInboundMailbox(bob.id, { address: 'inbox@scope-bob.example' });
  const aliceMessage = createInboundMessage(aliceMailbox, messageFixture('Alice message'));
  const bobMessage = createInboundMessage(bobMailbox, messageFixture('Bob message'));

  assert.throws(
    () => createApiToken(alice.id, 'Empty selection', {
      scopes: ['messages:read'],
      mailboxAccess: 'selected',
      mailboxIds: []
    }),
    /至少一个/
  );
  assert.throws(
    () => createApiToken(alice.id, 'Cross-user selection', {
      scopes: ['messages:read'],
      mailboxAccess: 'selected',
      mailboxIds: [bobMailbox.id]
    }),
    /不存在或无权访问/
  );
  assert.throws(
    () => createApiToken(alice.id, 'All mailboxes', {
      scopes: ['messages:read'],
      mailboxAccess: 'all'
    }),
    /只有管理员/
  );

  const aliceSelected = createApiToken(alice.id, 'Alice selected', {
    scopes: ['messages:read'],
    mailboxAccess: 'selected',
    mailboxIds: [aliceMailbox.id, aliceMailbox.id]
  });
  assert.deepEqual(aliceSelected.mailboxIds, [aliceMailbox.id]);
  assert.equal(searchInboundMessages(alice.id, {}, { mailboxIds: aliceSelected.mailboxIds }).total, 1);
  assert.equal(
    searchInboundMessages(
      alice.id,
      { mailboxId: bobMailbox.id },
      { mailboxIds: aliceSelected.mailboxIds }
    ).total,
    0
  );
  assert.equal(getInboundMessage(alice.id, bobMessage.id, { mailboxIds: aliceSelected.mailboxIds }), null);
  assert.equal(listInboundMailboxFolders(alice.id, bobMailbox.id, { mailboxIds: aliceSelected.mailboxIds }), null);

  const adminSelected = createApiToken(admin.id, 'Admin selected', {
    scopes: ['messages:read'],
    mailboxAccess: 'selected',
    mailboxIds: [bobMailbox.id]
  });
  const selectedAccess = { includeAllUsers: true, mailboxIds: adminSelected.mailboxIds };
  const selectedMessages = searchInboundMessages(admin.id, {}, selectedAccess);
  assert.equal(selectedMessages.total, 1);
  assert.equal(selectedMessages.messages[0].id, bobMessage.id);
  assert.equal(getInboundMessage(admin.id, aliceMessage.id, selectedAccess), null);
  assert.equal(getInboundMessage(admin.id, bobMessage.id, selectedAccess).subject, 'Bob message');
  assert.equal(listInboundMailboxFolders(admin.id, aliceMailbox.id, selectedAccess), null);
  assert.equal(
    listInboundMailboxFolders(admin.id, bobMailbox.id, selectedAccess)
      .find((folder) => folder.name === 'INBOX').messageCount,
    1
  );

  const adminAll = createApiToken(admin.id, 'Admin all', {
    scopes: ['messages:read'],
    mailboxAccess: 'all'
  });
  assert.equal(adminAll.mailboxAccess, 'all');
  assert.equal(searchInboundMessages(admin.id, {}, { includeAllUsers: true }).total, 2);
  assert.equal(listInboundMailboxes(admin.id, { includeAllUsers: true }).length, 2);

  assert.throws(() => updateUser(admin.id, { role: 'user' }), /至少保留一个/);
  createUser({
    username: 'scope-backup-admin',
    email: 'scope-backup-admin@example.com',
    password: 'password123',
    role: 'admin'
  });
  assert.equal(updateUser(admin.id, { role: 'user' }).role, 'user');
  assert.equal(authenticateApiToken(adminAll.token).user.role, 'user');

  database
    .prepare('UPDATE api_tokens SET mailbox_access_json = ? WHERE id = ?')
    .run('{"mode":"selected","mailboxIds":"broken"}', aliceSelected.id);
  const corrupted = getApiToken(aliceSelected.id, alice.id);
  assert.equal(corrupted.mailboxAccess, 'selected');
  assert.deepEqual(corrupted.mailboxIds, []);
  assert.equal(searchInboundMessages(alice.id, {}, { mailboxIds: corrupted.mailboxIds }).total, 0);
});

function tempDataDir() {
  return mkdtempSync(path.join(tmpdir(), 'mailhub-api-token-db-'));
}

function domainFixture(domain) {
  return {
    domain,
    selector: 'mh',
    verificationToken: `verify-${domain}`,
    dkimPublic: 'public',
    dkimPrivate: 'private',
    senderHost: `mail.${domain}`,
    sendingIp: '192.0.2.44',
    spfExtra: '',
    dmarcPolicy: 'none',
    dmarcRua: ''
  };
}

function messageFixture(subject) {
  return {
    sender: 'sender@example.net',
    recipients: ['inbox@example.net'],
    subject,
    messageId: `<${subject.toLowerCase().replaceAll(' ', '-')}@example.net>`,
    rawMessage: `Subject: ${subject}\r\n\r\n${subject}`,
    textBody: subject
  };
}
