import assert from 'node:assert/strict';
import {
  existsSync,
  mkdirSync,
  mkdtempSync,
  readFileSync,
  utimesSync,
  writeFileSync
} from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { test } from 'node:test';

import {
  createAtomicCheckpointWriter,
  parseVestaImportArguments,
  preflightVestaImport,
  runVestaMaildirImport
} from '../scripts/import-vesta-maildir.js';
import {
  getDomainByName,
  initDatabase,
  listInboundMailboxes,
  listInboundMessages,
  seedAdminUser
} from '../src/db.js';

const legacyHash = '{MD5}$1$hfT7jp2q$G3yf0NUx7mUkX.LIFWQxN.';

test('CLI arguments support production import paths and dry-run', () => {
  const parsed = parseVestaImportArguments([
    '--snapshot', 'snapshot',
    '--data-dir=data',
    '--user', 'admin',
    '--source', 'VESTA:Primary',
    '--checkpoint', 'state/checkpoint.json',
    '--dry-run'
  ], { cwd: '/tmp/mailhub-cli', env: {} });

  assert.equal(parsed.snapshot, '/tmp/mailhub-cli/snapshot');
  assert.equal(parsed.dataDir, '/tmp/mailhub-cli/data');
  assert.equal(parsed.user, 'admin');
  assert.equal(parsed.source, 'vesta:primary');
  assert.equal(parsed.checkpoint, '/tmp/mailhub-cli/state/checkpoint.json');
  assert.equal(parsed.dryRun, true);
});

test('dry-run checks all 35 hashes without business writes, then real import resumes safely', async () => {
  const fixture = createSnapshotFixture(35);
  const dataDir = mkdtempSync(path.join(os.tmpdir(), 'mailhub-vesta-cli-db-'));
  initDatabase(dataDir, 'cli-test-secret');
  const user = seedAdminUser({
    username: 'admin',
    email: 'admin@example.test',
    password: 'admin-password'
  });
  const checkpoint = path.join(dataDir, 'import.checkpoint.json');
  const output = captureOutput();
  const before = businessCounts(dataDir);
  const common = {
    snapshot: fixture.root,
    dataDir,
    user: 'admin',
    source: 'vesta:test',
    checkpoint
  };

  const dryRun = await runVestaMaildirImport({ ...common, dryRun: true }, {
    env: { MAIL_HOSTNAME: 'smtp.target.test', SENDING_IP: '192.0.2.44' },
    output
  });
  assert.deepEqual(businessCounts(dataDir), before);
  assert.equal(dryRun.preflight.hashes, 35);
  assert.equal(dryRun.preflight.supportedHashes, 35);
  assert.equal(dryRun.preflight.unsupportedHashes, 0);
  assert.equal(dryRun.summary.plannedMessages, 1);
  assert.equal(dryRun.summary.importedMessages, 0);
  assert.equal(existsSync(checkpoint), false);
  assert.match(output.text(), /preflight .*hashes=35 .*supported_hashes=35/);
  assert.match(output.text(), /complete dry_run=1/);
  assertNoSensitiveOutput(output.text());

  output.clear();
  const imported = await runVestaMaildirImport(common, {
    env: {
      SESSION_SECRET: 'cli-test-secret',
      MAIL_HOSTNAME: 'smtp.target.test',
      SENDING_IP: '192.0.2.44',
      DEFAULT_SPF_MECHANISMS: 'ip4:192.0.2.44',
      DMARC_POLICY: 'quarantine',
      DMARC_RUA: 'mailto:dmarc@target.test'
    },
    output
  });
  assert.equal(imported.summary.importedMessages, 1);
  assert.equal(listInboundMailboxes(user.id).length, 35);
  assert.equal(listInboundMessages(user.id, { folder: null }).length, 1);
  const domain = getDomainByName('import.example', { userId: user.id });
  assert.equal(domain.catchAllAddress, 'acct00@import.example');
  assert.equal(domain.senderHost, 'smtp.target.test');
  assert.equal(domain.sendingIp, '192.0.2.44');
  assert.equal(domain.spfExtra, 'ip4:192.0.2.44');
  assert.equal(domain.dmarcPolicy, 'quarantine');
  assert.equal(domain.dmarcRua, 'mailto:dmarc@target.test');
  assertNoSensitiveOutput(output.text());

  const checkpointValue = JSON.parse(readFileSync(checkpoint, 'utf8'));
  assert.equal(checkpointValue.complete, true);
  assert.equal(checkpointValue.processed, 1);
  assert.equal(typeof checkpointValue.lastSourceKey, 'string');
  assert.equal('warnings' in checkpointValue, false);
  assert.equal(checkpointValue.warningCount, 34);
  assertNoSensitiveOutput(JSON.stringify(checkpointValue));

  output.clear();
  const resumed = await runVestaMaildirImport(common, {
    env: { SESSION_SECRET: 'cli-test-secret' },
    output
  });
  assert.equal(resumed.summary.importedMessages, 0);
  assert.equal(resumed.summary.skippedMessages, 1);
  assert.equal(resumed.summary.resumeSkippedMessages, 0);
  assert.equal(listInboundMessages(user.id, { folder: null }).length, 1);
  assertNoSensitiveOutput(output.text());

  writeFileSync(
    path.join(fixture.root, 'usr/local/vesta/data/users/admin/mail.conf'),
    "DOMAIN='import.example' CATCHALL='acct01' SUSPENDED='no'\n"
  );
  await assert.rejects(
    runVestaMaildirImport(common, {
      env: { SESSION_SECRET: 'cli-test-secret' },
      output
    }),
    (error) => error?.category === 'checkpoint'
  );
  assert.equal(getDomainByName('import.example', { userId: user.id }).catchAllAddress, 'acct00@import.example');
});

test('preflight counts unsupported hashes and cross-user conflicts without exposing resource values', () => {
  const result = preflightVestaImport({
    domains: [{ domain: 'conflict.example' }],
    mailboxes: [{ address: 'private@conflict.example', passwordHash: '{SHA512-CRYPT}redacted' }],
    warnings: ['sensitive warning']
  }, {
    user: { id: 7 },
    domains: [{ domain: 'conflict.example', userId: 8 }],
    mailboxes: [{ address: 'private@conflict.example', userId: 8 }]
  });

  assert.equal(result.conflicts, 2);
  assert.equal(result.hashes, 1);
  assert.equal(result.unsupportedHashes, 1);
  assert.equal(result.warningCount, 1);
  assert.equal('warnings' in result, false);
});

test('preflight blocks missing hashes and invalid defaults for new domains', () => {
  const result = preflightVestaImport({
    domains: [{ domain: 'new.example' }],
    mailboxes: [{ address: 'missing@new.example', passwordHash: '' }],
    warnings: []
  }, {
    user: { id: 7 },
    domains: [],
    mailboxes: []
  }, {
    defaults: { senderHost: 'invalid host', sendingIp: '' }
  });

  assert.equal(result.newDomains, 1);
  assert.equal(result.missingHashes, 1);
  assert.equal(result.invalidDefaults, 1);
});

test('preflight blocks a soft-deleted mailbox that the import cannot reuse', () => {
  const result = preflightVestaImport({
    domains: [{ domain: 'existing.example' }],
    mailboxes: [{ address: 'archived@existing.example', passwordHash: legacyHash }],
    warnings: []
  }, {
    user: { id: 7 },
    domains: [{ domain: 'existing.example', userId: 7 }],
    mailboxes: [{
      address: 'archived@existing.example',
      userId: 7,
      deletedAt: '2026-07-15T00:00:00.000Z'
    }]
  });

  assert.equal(result.conflicts, 1);
});

test('preflight only allows an existing same-owner mailbox during checkpoint resume', () => {
  const snapshot = {
    domains: [{ domain: 'existing.example' }],
    mailboxes: [{ address: 'owner@existing.example', passwordHash: legacyHash }],
    warnings: []
  };
  const inventory = {
    user: { id: 7 },
    domains: [{ domain: 'existing.example', userId: 7 }],
    mailboxes: [{ address: 'owner@existing.example', userId: 7, deletedAt: null }]
  };

  const firstRun = preflightVestaImport(snapshot, inventory);
  const resume = preflightVestaImport(snapshot, inventory, { allowExistingMailboxes: true });

  assert.equal(firstRun.existingMailboxes, 1);
  assert.equal(firstRun.conflicts, 1);
  assert.equal(resume.existingMailboxes, 1);
  assert.equal(resume.conflicts, 0);
});

test('real import writes an incomplete checkpoint before entering the importer', async () => {
  const directory = mkdtempSync(path.join(os.tmpdir(), 'mailhub-vesta-initial-checkpoint-'));
  const checkpoint = path.join(directory, 'checkpoint.json');
  const user = { id: 7, username: 'admin', email: 'admin@example.test', status: 'active' };
  const snapshot = {
    domains: [{ domain: 'new.example' }],
    mailboxes: [{ address: 'owner@new.example', passwordHash: legacyHash }],
    warnings: ['count only']
  };

  await assert.rejects(
    runVestaMaildirImport({
      snapshot: path.join(directory, 'snapshot'),
      dataDir: directory,
      user: 'admin',
      source: 'vesta:checkpoint',
      checkpoint
    }, {
      env: { MAIL_HOSTNAME: 'smtp.target.test', SENDING_IP: '192.0.2.44' },
      inspectTarget: () => ({ user, domains: [], mailboxes: [] }),
      readSnapshot: async () => snapshot,
      dbApi: {
        initDatabase() {},
        getUserByLogin: () => user
      },
      importSnapshot: async () => {
        const initial = JSON.parse(readFileSync(checkpoint, 'utf8'));
        assert.equal(initial.complete, false);
        assert.equal(initial.lastSourceKey, '');
        assert.equal(initial.processed, 0);
        assert.equal(initial.warningCount, 1);
        throw new Error('simulated mailbox-stage crash');
      },
      output: captureOutput()
    }),
    /simulated mailbox-stage crash/
  );

  assert.equal(JSON.parse(readFileSync(checkpoint, 'utf8')).complete, false);
});

test('checkpoint writer atomically flushes every 100 messages or one second', async () => {
  const directory = mkdtempSync(path.join(os.tmpdir(), 'mailhub-vesta-checkpoint-'));
  const filePath = path.join(directory, 'checkpoint.json');
  let timestamp = 0;
  const writer = createAtomicCheckpointWriter({
    filePath,
    identity: { version: 1, source: 'vesta:test', targetUserId: 1, snapshotId: 'snapshot-id' },
    clock: () => timestamp
  });

  for (let index = 1; index < 100; index += 1) {
    assert.equal(await writer.record(`source-${index}`, { processed: index, bytes: index }), false);
  }
  assert.equal(existsSync(filePath), false);
  assert.equal(await writer.record('source-100', { processed: 100, bytes: 100 }), true);
  assert.equal(JSON.parse(readFileSync(filePath, 'utf8')).processed, 100);

  timestamp = 1000;
  assert.equal(await writer.record('source-101', { processed: 101, bytes: 101 }), true);
  const checkpoint = JSON.parse(readFileSync(filePath, 'utf8'));
  assert.equal(checkpoint.processed, 101);
  assert.equal(checkpoint.lastSourceKey, 'source-101');
  assert.equal(checkpoint.complete, false);
});

function createSnapshotFixture(accountCount) {
  const root = mkdtempSync(path.join(os.tmpdir(), 'mailhub-vesta-cli-snapshot-'));
  const userData = path.join(root, 'usr/local/vesta/data/users/admin');
  const accountConfig = path.join(userData, 'mail');
  const mailRoot = path.join(root, 'home/admin/mail/import.example');
  const firstMailbox = path.join(mailRoot, 'acct00');
  for (const directory of [
    accountConfig,
    path.join(firstMailbox, 'cur'),
    path.join(firstMailbox, 'new'),
    path.join(firstMailbox, 'tmp')
  ]) mkdirSync(directory, { recursive: true });
  writeFileSync(
    path.join(userData, 'mail.conf'),
    "DOMAIN='import.example' CATCHALL='acct00' SUSPENDED='no'\n"
  );
  const accounts = [];
  for (let index = 0; index < accountCount; index += 1) {
    const account = `acct${String(index).padStart(2, '0')}`;
    accounts.push(
      `ACCOUNT='${account}' MD5='${legacyHash}' QUOTA='unlimited' SUSPENDED='no' DATE='2024-01-02' TIME='03:04:05'`
    );
  }
  writeFileSync(path.join(accountConfig, 'import.example.conf'), `${accounts.join('\n')}\n`);
  const messagePath = path.join(firstMailbox, 'cur', '1700000000.M1P1.host:2,S');
  writeFileSync(messagePath, [
    'From: sender@example.net',
    'To: acct00@import.example',
    'Subject: Private migration subject',
    'Message-ID: <private-import@example.net>',
    '',
    'Private migration body'
  ].join('\r\n'));
  const receivedAt = new Date('2024-01-02T03:04:05.000Z');
  utimesSync(messagePath, receivedAt, receivedAt);
  return { root };
}

function businessCounts(dataDir) {
  const database = new DatabaseSync(path.join(dataDir, 'mailhub.sqlite'), { readOnly: true });
  try {
    return {
      domains: Number(database.prepare('SELECT COUNT(*) AS count FROM domains').get().count),
      mailboxes: Number(database.prepare('SELECT COUNT(*) AS count FROM inbound_mailboxes').get().count),
      messages: Number(database.prepare('SELECT COUNT(*) AS count FROM inbound_messages').get().count)
    };
  } finally {
    database.close();
  }
}

function captureOutput() {
  let value = '';
  return {
    write(chunk) {
      value += String(chunk);
      return true;
    },
    text() {
      return value;
    },
    clear() {
      value = '';
    }
  };
}

function assertNoSensitiveOutput(value) {
  assert.doesNotMatch(value, /acct00@import\.example/i);
  assert.doesNotMatch(value, /\$1\$hfT7jp2q/i);
  assert.doesNotMatch(value, /Private migration (?:subject|body)/i);
}
