/**
 * Backup notification service check — real service, stubbed model layer and
 * mailer. Verifies the decision logic, recipient union, retry and logging.
 */
process.env.ENCRYPTION_KEY = 'backup-notify-check-key-32-chars-plus!!';
process.env.NODE_ENV = 'test';

import path from 'path';

const BASE = path.resolve(__dirname, '../..');
/* eslint-disable @typescript-eslint/no-var-requires */

let fail = 0;
const check = (ok: boolean, label: string) => { if (!ok) fail++; console.log(`${ok ? 'PASS' : 'FAIL'}  ${label}`); };

const store: Record<string, any[]> = { users: [], companies: [], backups: [], backupSettings: [], backupNotificationLogs: [] };
const matches = (d: any, q: any) => Object.entries(q || {}).every(([k, v]) => String(k === '_id' ? d._id : d[k]) === String(v));
const chain = (r: any) => ({ select: () => chain(r), sort: () => chain(r), lean: async () => r, catch: () => chain(r), then: (f: any) => Promise.resolve(r).then(f) });
const model = (coll: string) => ({
  findById: (id: any) => chain(store[coll].find(d => String(d._id) === String(id)) || null),
  findOne: (q: any) => chain(store[coll].find(d => matches(d, q)) || null),
  find: (q: any) => chain(store[coll].filter(d => matches(d, q))),
  create: async (data: any) => { const d = { _id: `l${store[coll].length + 1}`, createdAt: new Date(), ...data }; store[coll].push(d); return d; },
});

const modelsPath = require.resolve(`${BASE}/models`);
require.cache[modelsPath] = { id: modelsPath, filename: modelsPath, loaded: true, exports: {
  getModels: () => ({
    User: model('users'), Company: model('companies'), Backup: model('backups'),
    BackupSettings: model('backupSettings'), BackupNotificationLog: model('backupNotificationLogs'),
  }),
} } as any;

// Stub the mailer so nothing leaves the machine and failures are scriptable.
let mailBehaviour: 'ok' | 'fail' | 'skip' | 'failThenOk' = 'ok';
let mailCalls: any[] = [];
const emailsPath = require.resolve(`${BASE}/services/email/systemEmails`);
require.cache[emailsPath] = { id: emailsPath, filename: emailsPath, loaded: true, exports: {
  sendNotificationEmail: async (p: any) => {
    mailCalls.push(p);
    if (mailBehaviour === 'skip') return { success: false, skipped: true, error: 'SMTP not configured' };
    if (mailBehaviour === 'fail') return { success: false, error: 'Connection refused' };
    if (mailBehaviour === 'failThenOk') return mailCalls.length < 2 ? { success: false, error: 'Temporary failure' } : { success: true, messageId: 'mid-2' };
    return { success: true, messageId: 'mid-1' };
  },
  sendTwoFactorStatusEmail: async () => ({ success: true }),
} } as any;

const { notifyBackupResult, sendTestBackupNotification } = require(`${BASE}/services/backup/backupNotifications`);
const { invalidateBackupNotificationSettingsCache } = require(`${BASE}/services/backup/backupNotificationSettings`);

const setConfig = (cfg: any) => {
  store.users[0].panelSettings = { backupNotifications: cfg };
  invalidateBackupNotificationSettingsCache();
};
const reset = () => { store.backupNotificationLogs.length = 0; mailCalls = []; mailBehaviour = 'ok'; };
const lastLog = () => store.backupNotificationLogs[store.backupNotificationLogs.length - 1];

(async () => {
  store.users.push({ _id: 'sa1', role: 'super-admin', panelSettings: {} });
  store.companies.push({ _id: 'c1', name: 'Acme Corp' });
  store.backups.push({
    _id: 'b1', companyId: 'c1', name: 'Nightly Backup', type: 'automatic', status: 'completed',
    size: 52428800, duration: 84000, filePath: '/backups/b1.zip',
    stats: { dbRecords: 12480, files: 317 }, updatedAt: new Date(),
  });
  store.backups.push({
    _id: 'b2', companyId: 'c1', name: 'Manual Backup', type: 'manual', status: 'failed',
    errorMessage: 'Disk full', duration: 4200, updatedAt: new Date(),
  });
  store.backupSettings.push({ _id: 's1', companyId: 'c1', notifyOnComplete: true, notifyEmail: 'legacy@acme.com', notifyEmails: ['ops@acme.com'] });

  // ── disabled by default ────────────────────────────────────────────────────
  console.log('\n-- master switch --');
  reset(); setConfig({ enabled: false });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 0, 'no email when notifications are disabled');
  check(lastLog()?.skipped === true && /disabled/i.test(lastLog().skipReason), `skip is logged with a reason -> "${lastLog()?.skipReason}"`);

  // ── success ────────────────────────────────────────────────────────────────
  console.log('\n-- success notification --');
  reset(); setConfig({ enabled: true, recipients: ['admin@x.com'] });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 1, 'one email sent');
  check(mailCalls[0].template === 'backup-success', 'uses the success template');
  check(mailCalls[0].subject === 'Backup completed: Nightly Backup', `subject token rendered -> "${mailCalls[0].subject}"`);

  const v = mailCalls[0].variables;
  check(v.backup_name === 'Nightly Backup', 'variable: backup name');
  check(v.backup_type === 'Automatic', 'variable: backup type');
  check(v.backup_status === 'Completed', 'variable: status');
  check(v.backup_size === '50.00 MB', `variable: size -> ${v.backup_size}`);
  check(v.backup_duration === '1m 24s', `variable: duration -> ${v.backup_duration}`);
  check(v.backup_records === '12480', 'variable: record count');
  check(v.storage_location === 'Server storage', 'variable: storage location');
  check(v.company_name === 'Acme Corp', 'variable: company name');
  check(v.failure_reason === '', 'variable: no failure reason on success');
  check(v.server_host === '', 'variable: server info omitted by default');

  // recipient union + dedupe
  const to = mailCalls[0].to;
  check(Array.isArray(to) && to.includes('admin@x.com') && to.includes('ops@acme.com') && to.includes('legacy@acme.com'),
    `recipients unioned (global + notifyEmails + legacy notifyEmail) -> ${JSON.stringify(to)}`);
  check(new Set(to).size === to.length, 'recipients de-duplicated');
  check(lastLog().success === true && lastLog().attempts === 1, 'success logged with attempt count');

  // ── duplicate guard ────────────────────────────────────────────────────────
  mailCalls = [];
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 0, 'no duplicate email for the same backup + event');

  // ── failure ────────────────────────────────────────────────────────────────
  console.log('\n-- failure notification --');
  reset(); setConfig({ enabled: true, recipients: ['admin@x.com'] });
  await notifyBackupResult({ backupId: 'b2', status: 'failed' });
  check(mailCalls[0].template === 'backup-failure', 'uses the failure template');
  check(mailCalls[0].subject === 'Backup FAILED: Manual Backup', `failure subject -> "${mailCalls[0].subject}"`);
  check(mailCalls[0].variables.failure_reason === 'Disk full', 'failure reason included');
  check(mailCalls[0].variables.backup_type === 'Manual', 'manual backup type reported');

  // ── per-event toggles ──────────────────────────────────────────────────────
  console.log('\n-- per-event toggles --');
  reset(); setConfig({ enabled: true, recipients: ['a@x.com'], notifyOnSuccess: false });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 0 && lastLog().skipped, 'success suppressed when notifyOnSuccess is off');
  await notifyBackupResult({ backupId: 'b2', status: 'failed' });
  check(mailCalls.length === 1, 'failure still sent when only success is off');

  reset(); setConfig({ enabled: true, recipients: ['a@x.com'], notifyOnFailure: false });
  await notifyBackupResult({ backupId: 'b2', status: 'failed' });
  check(mailCalls.length === 0, 'failure suppressed when notifyOnFailure is off');

  // ── recipients ─────────────────────────────────────────────────────────────
  console.log('\n-- recipients --');
  reset(); setConfig({ enabled: true, recipients: [] , includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 0 && /no recipient/i.test(lastLog().skipReason), 'no recipients → skipped and logged');

  reset(); setConfig({ enabled: true, recipients: ['ok@x.com', 'not-an-email', ''], includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(JSON.stringify(mailCalls[0].to) === JSON.stringify(['ok@x.com']), `malformed addresses filtered out -> ${JSON.stringify(mailCalls[0].to)}`);

  reset(); setConfig({ enabled: true, recipients: ['g@x.com'], includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(!mailCalls[0].to.includes('ops@acme.com'), 'company recipients excluded when the toggle is off');

  // ── notify whoever ran it ──────────────────────────────────────────────────
  console.log('\n-- backup creator --');
  store.users.push({ _id: 'u9', email: 'runner@acme.com', role: 'admin' });
  store.backups.push({
    _id: 'b3', companyId: 'c1', name: 'Ad-hoc Backup', type: 'manual', status: 'completed',
    size: 1024, duration: 2000, filePath: '/backups/b3.zip', createdBy: 'u9',
    stats: { dbRecords: 5, files: 1 }, updatedAt: new Date(),
  });

  reset(); setConfig({ enabled: true, recipients: [], includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b3', status: 'completed' });
  check(mailCalls.length === 1 && mailCalls[0].to.includes('runner@acme.com'),
    `manual backup notifies whoever ran it with no recipients configured -> ${JSON.stringify(mailCalls[0]?.to)}`);

  reset(); setConfig({ enabled: true, recipients: ['admin@x.com'], includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b3', status: 'completed' });
  check(mailCalls[0].to.includes('runner@acme.com') && mailCalls[0].to.includes('admin@x.com'),
    'creator is ADDED to the configured list, not substituted for it');

  reset(); setConfig({ enabled: true, recipients: ['runner@acme.com'], includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b3', status: 'completed' });
  check(mailCalls[0].to.filter((e: string) => e === 'runner@acme.com').length === 1,
    'a creator already on the list is not emailed twice');

  reset(); setConfig({ enabled: true, recipients: [], includeCompanyRecipients: false, notifyBackupCreator: false });
  await notifyBackupResult({ backupId: 'b3', status: 'completed' });
  check(mailCalls.length === 0 && /no recipient/i.test(lastLog().skipReason),
    'creator not notified when the toggle is off');

  // An automatic backup has no person behind it — configured recipients remain
  // the only way a scheduled failure ever gets reported.
  reset(); setConfig({ enabled: true, recipients: [], includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 0 && /no recipient/i.test(lastLog().skipReason),
    'automatic backup (createdBy: system) has no creator to notify');

  // A deleted user must not lose the email for everyone else.
  store.backups.push({
    _id: 'b4', companyId: 'c1', name: 'Orphan Backup', type: 'manual', status: 'completed',
    size: 1024, duration: 2000, createdBy: 'deleted-user-id', updatedAt: new Date(),
  });
  reset(); setConfig({ enabled: true, recipients: ['admin@x.com'], includeCompanyRecipients: false });
  await notifyBackupResult({ backupId: 'b4', status: 'completed' });
  check(mailCalls.length === 1 && mailCalls[0].to.includes('admin@x.com'),
    'an unresolvable creator does not block the configured recipients');

  // ── retry ──────────────────────────────────────────────────────────────────
  console.log('\n-- retry --');
  reset(); setConfig({ enabled: true, recipients: ['a@x.com'], retry: { maxAttempts: 3, baseDelayMs: 0 } });
  mailBehaviour = 'fail';
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 3, `retried up to maxAttempts -> ${mailCalls.length} attempts`);
  check(lastLog().success === false && lastLog().attempts === 3 && !!lastLog().error, 'failure logged with attempts and error');

  reset(); setConfig({ enabled: true, recipients: ['a@x.com'], retry: { maxAttempts: 3, baseDelayMs: 0 } });
  mailBehaviour = 'failThenOk';
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 2 && lastLog().success === true, 'stops retrying once a send succeeds');

  reset(); setConfig({ enabled: true, recipients: ['a@x.com'], retry: { maxAttempts: 3, baseDelayMs: 0 } });
  mailBehaviour = 'skip';
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls.length === 1, 'no retry when SMTP is unconfigured (retrying cannot help)');

  // ── custom subject / branding / server info ───────────────────────────────
  console.log('\n-- customisation --');
  reset(); setConfig({
    enabled: true, recipients: ['a@x.com'], includeServerInfo: true,
    subjectSuccess: '[{{company_name}}] {{backup_name}} — {{backup_size}}',
    footerHtml: '<b>Acme IT</b>', logoUrl: 'https://x.com/logo.png',
  });
  await notifyBackupResult({ backupId: 'b1', status: 'completed' });
  check(mailCalls[0].subject === '[Acme Corp] Nightly Backup — 50.00 MB', `custom subject rendered -> "${mailCalls[0].subject}"`);
  check(mailCalls[0].variables.footer_html === '<b>Acme IT</b>', 'footer branding passed through');
  check(mailCalls[0].variables.logo_url === 'https://x.com/logo.png', 'logo url passed through');
  check(!!mailCalls[0].variables.server_host, 'server info included when enabled');

  // ── never throws ───────────────────────────────────────────────────────────
  console.log('\n-- resilience --');
  reset(); setConfig({ enabled: true, recipients: ['a@x.com'] });
  let threw = false;
  try { await notifyBackupResult({ backupId: 'does-not-exist', status: 'completed' }); } catch { threw = true; }
  check(!threw, 'unknown backup id does not throw');
  try { await notifyBackupResult({ backupId: null as any, status: 'failed' }); } catch { threw = true; }
  check(!threw, 'null backup id does not throw');

  // ── test email ─────────────────────────────────────────────────────────────
  console.log('\n-- test email --');
  reset(); setConfig({ enabled: true, recipients: [] });
  let t = await sendTestBackupNotification({ to: 'admin@x.com' });
  check(t.success === true, 'test email sends even with no recipients configured');
  check(/^\[Test\]/.test(mailCalls[0].subject), `test subject is marked -> "${mailCalls[0].subject}"`);
  check(mailCalls[0].variables.backup_name === 'Test Backup - Sample', 'test uses sample data');

  reset(); mailBehaviour = 'skip';
  t = await sendTestBackupNotification({ to: 'admin@x.com' });
  check(t.success === false && /SMTP/i.test(t.message), `unconfigured SMTP reported clearly -> "${t.message}"`);

  reset(); mailBehaviour = 'ok';
  t = await sendTestBackupNotification({ to: 'admin@x.com', notificationType: 'failure' });
  check(mailCalls[0].template === 'backup-failure' && !!mailCalls[0].variables.failure_reason, 'failure variant testable');

  console.log(fail === 0 ? '\nAll checks passed.' : `\n${fail} check(s) FAILED.`);
  process.exit(fail ? 1 : 0);
})().catch(e => { console.error(e); process.exit(1); });
