/**
 * Email Configuration Routes (SMTP)
 * Super Admin only.
 *
 * Several configurations can exist at once — Brevo, Zoho Mail, a backup relay —
 * each with its own credentials and sender identity. Exactly one is the default,
 * and that is the one every transactional email is sent through.
 *
 * Passwords are encrypted at rest and never returned; the client receives a
 * `hasPassword` flag instead, and omitting the field on update keeps the stored
 * one. Any write invalidates the mailer's cached transport, so the next email
 * uses the current default without a restart.
 */

import express, { Request, Response } from 'express';
import { body, param, validationResult } from 'express-validator';
import { authenticate, requireRole } from '../middleware/auth';
import { getModels } from '../models';
import { encrypt } from '../utils/encryption';
import { SMTP_PROVIDERS, getSmtpProviderPreset } from '../services/email/smtpProviders';
import {
  invalidateMailerCache,
  verifySmtpConnection,
  sendSystemEmail,
  loadSmtpSettings,
  loadSmtpSettingsById,
  isMailerDependencyAvailable,
  MAILER_DEPENDENCY_MESSAGE,
  buildTransport,
  type SmtpSettings,
} from '../services/email/transactionalMailer';
import { SYSTEM_EMAIL_TEMPLATES } from '../services/email/systemEmailTemplates';

const router = express.Router();

router.use(authenticate, requireRole('super-admin'));

const PROVIDER_IDS = SMTP_PROVIDERS.map(p => p.id);

/** Shape returned to the client — never includes the password itself. */
function presentConfig(config: any) {
  return {
    id: String(config?._id || config?.id || ''),
    name: config?.name || '',
    provider: config?.provider || 'brevo',
    host: config?.host || '',
    port: config?.port ?? 587,
    encryption: config?.encryption || 'tls',
    username: config?.username || '',
    // The password is write-only. The UI shows "saved" state from this flag.
    hasPassword: !!config?.password,
    senderName: config?.senderName || '',
    senderEmail: config?.senderEmail || '',
    replyToEmail: config?.replyToEmail || '',
    isActive: config?.isActive === true,
    isDefault: config?.isDefault === true,
    rejectUnauthorized: config?.rejectUnauthorized !== false,
    lastTestedAt: config?.lastTestedAt || null,
    lastTestSuccess: config?.lastTestSuccess ?? null,
    lastTestMessage: config?.lastTestMessage || '',
    updatedAt: config?.updatedAt || null,
  };
}

/** A configuration must be usable before it can be enabled or made default. */
function missingRequiredFields(config: any): string[] {
  const missing: string[] = [];
  if (!config.host) missing.push('host');
  if (!config.port) missing.push('port');
  if (!config.senderEmail) missing.push('sender email');
  return missing;
}

/**
 * Make one configuration the default, clearing the flag on every other row.
 * Nothing else may hold it — sending has to be unambiguous.
 */
async function promoteToDefault(id: string): Promise<void> {
  const { SmtpConfig } = getModels();
  await SmtpConfig.updateMany({ _id: { $ne: id }, isDefault: true }, { $set: { isDefault: false } });
  await SmtpConfig.findByIdAndUpdate(id, { $set: { isDefault: true } });
}

/**
 * Keep the collection coherent after any change: if nothing is the default,
 * promote the first enabled configuration so email never silently stops.
 */
async function ensureDefaultExists(): Promise<void> {
  const { SmtpConfig } = getModels();
  const currentDefault = await SmtpConfig.findOne({ isDefault: true, isActive: true });
  if (currentDefault) return;

  const candidate = await SmtpConfig.findOne({ isActive: true });
  if (candidate) {
    await promoteToDefault(String(candidate._id));
    console.log(`[SmtpConfig] Auto-promoted "${candidate.name || candidate.host}" to default`);
  }
}

// ============================================
// Reference data
// ============================================

// GET /providers — Presets for the provider picker
router.get('/providers', (_req: Request, res: Response) => {
  res.json({ data: SMTP_PROVIDERS });
});

// GET /templates — Which system emails route through the default configuration
router.get('/templates', (_req: Request, res: Response) => {
  res.json({
    data: SYSTEM_EMAIL_TEMPLATES.map(t => ({ slug: t.slug, name: t.name, subject: t.subject })),
  });
});

// GET /status — Server-side readiness, independent of any one configuration
router.get('/status', async (_req: Request, res: Response) => {
  try {
    const available = isMailerDependencyAvailable();
    const active = await loadSmtpSettings();
    res.json({
      data: {
        mailerAvailable: available,
        mailerUnavailableReason: available ? '' : MAILER_DEPENDENCY_MESSAGE,
        activeConfigName: active?.name || '',
        activeConfigProvider: active?.provider || '',
        ready: available && !!active,
      },
    });
  } catch (error: any) {
    res.status(500).json({ error: error?.message || 'Failed to read email status' });
  }
});

// ============================================
// Configurations
// ============================================

// GET / — All configurations
router.get('/', async (_req: Request, res: Response) => {
  try {
    const { SmtpConfig } = getModels();

    // A configuration saved before multi-provider support carries no isDefault
    // flag. Promote it on first read so it shows as the sending one rather than
    // appearing inert.
    await ensureDefaultExists();

    const configs = await SmtpConfig.find({}).sort({ isDefault: -1, createdAt: 1 });
    const available = isMailerDependencyAvailable();

    res.json({
      data: (configs || []).map((c: any) => presentConfig(c.toObject ? c.toObject() : c)),
      // Server-side blocker — no configuration can send without the mail library.
      mailerAvailable: available,
      mailerUnavailableReason: available ? '' : MAILER_DEPENDENCY_MESSAGE,
    });
  } catch (error: any) {
    console.error('[SmtpConfig] Fetch failed:', error?.message);
    res.status(500).json({ error: 'Failed to load email configurations' });
  }
});

const configValidation = [
  body('name').optional().trim().isLength({ max: 80 }).withMessage('Name cannot exceed 80 characters'),
  body('provider').optional().isIn(PROVIDER_IDS).withMessage(`Provider must be one of: ${PROVIDER_IDS.join(', ')}`),
  body('host').optional().trim().isLength({ max: 255 }).withMessage('Host cannot exceed 255 characters'),
  body('port').optional().isInt({ min: 1, max: 65535 }).withMessage('Port must be between 1 and 65535'),
  body('encryption').optional().isIn(['none', 'tls', 'ssl']).withMessage('Encryption must be none, tls or ssl'),
  body('username').optional().trim().isLength({ max: 255 }),
  body('password').optional().isString(),
  body('senderName').optional().trim().isLength({ max: 100 }).withMessage('Sender name cannot exceed 100 characters'),
  body('senderEmail').optional({ checkFalsy: true }).trim().isEmail().withMessage('Sender email must be a valid address'),
  body('replyToEmail').optional({ checkFalsy: true }).trim().isEmail().withMessage('Reply-to must be a valid address'),
  body('isActive').optional().isBoolean(),
  body('isDefault').optional().isBoolean(),
  body('rejectUnauthorized').optional().isBoolean(),
];

/** Copy submitted fields onto a document. Password only when newly supplied. */
function applyBody(config: any, b: Record<string, any>): void {
  if (b.name !== undefined) config.name = String(b.name).trim();
  if (b.provider !== undefined) config.provider = b.provider;
  if (b.host !== undefined) config.host = String(b.host).trim();
  if (b.port !== undefined) config.port = Number(b.port);
  if (b.encryption !== undefined) config.encryption = b.encryption;
  if (b.username !== undefined) config.username = String(b.username).trim();
  if (b.senderName !== undefined) config.senderName = String(b.senderName).trim();
  if (b.senderEmail !== undefined) config.senderEmail = String(b.senderEmail).trim().toLowerCase();
  if (b.replyToEmail !== undefined) config.replyToEmail = String(b.replyToEmail).trim().toLowerCase();
  if (b.isActive !== undefined) config.isActive = !!b.isActive;
  if (b.rejectUnauthorized !== undefined) config.rejectUnauthorized = !!b.rejectUnauthorized;

  // Blank/omitted password keeps the stored one, so the host or sender can be
  // edited without re-entering credentials.
  if (typeof b.password === 'string' && b.password.trim() !== '') {
    config.password = encrypt(b.password);
  }
}

// POST / — Add a configuration
router.post('/', configValidation, async (req: Request, res: Response) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({
        error: 'Validation failed',
        details: errors.array().map((e: any) => ({ field: e.path || e.param, message: e.msg })),
      });
      return;
    }

    const { SmtpConfig } = getModels();
    const b = req.body || {};

    const config = new SmtpConfig({});
    applyBody(config, b);

    if (!config.name) {
      const preset = getSmtpProviderPreset(config.provider);
      config.name = preset?.label || config.host || 'SMTP configuration';
    }

    if (config.isActive) {
      const missing = missingRequiredFields(config);
      if (missing.length > 0) {
        res.status(400).json({ error: `Cannot enable this configuration — missing: ${missing.join(', ')}.` });
        return;
      }
    }

    config.updatedBy = req.user?.id;
    await config.save();

    // First usable configuration becomes the default automatically; an explicit
    // request to make it default is honoured too.
    const existingDefault = await SmtpConfig.findOne({ isDefault: true, isActive: true, _id: { $ne: config._id } });
    if (config.isActive && (b.isDefault === true || !existingDefault)) {
      await promoteToDefault(String(config._id));
    }
    await ensureDefaultExists();
    invalidateMailerCache();

    const saved = await SmtpConfig.findById(config._id);
    console.log(`[SmtpConfig] Created "${config.name}" by ${req.user?.email}`);
    res.status(201).json({
      data: presentConfig(saved?.toObject ? saved.toObject() : saved),
      message: 'Email configuration added.',
    });
  } catch (error: any) {
    console.error('[SmtpConfig] Create failed:', error?.message);
    res.status(500).json({ error: error?.message || 'Failed to add the email configuration' });
  }
});

// PUT /:id — Update a configuration
router.put(
  '/:id',
  [param('id').notEmpty().withMessage('Configuration id is required'), ...configValidation],
  async (req: Request, res: Response) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        res.status(400).json({
          error: 'Validation failed',
          details: errors.array().map((e: any) => ({ field: e.path || e.param, message: e.msg })),
        });
        return;
      }

      const { SmtpConfig } = getModels();
      const config = await SmtpConfig.findById(req.params.id);
      if (!config) {
        res.status(404).json({ error: 'Email configuration not found' });
        return;
      }

      const b = req.body || {};
      const wasDefault = config.isDefault === true;
      applyBody(config, b);

      if (config.isActive) {
        const missing = missingRequiredFields(config);
        if (missing.length > 0) {
          res.status(400).json({ error: `Cannot enable this configuration — missing: ${missing.join(', ')}.` });
          return;
        }
      }

      // Disabling the default leaves nothing sending — hand the role over below.
      if (!config.isActive && wasDefault) config.isDefault = false;

      config.updatedBy = req.user?.id;
      await config.save();

      if (b.isDefault === true && config.isActive) {
        await promoteToDefault(String(config._id));
      }
      await ensureDefaultExists();
      invalidateMailerCache();

      const saved = await SmtpConfig.findById(config._id);
      console.log(`[SmtpConfig] Updated "${config.name}" by ${req.user?.email}`);
      res.json({
        data: presentConfig(saved?.toObject ? saved.toObject() : saved),
        message: 'Email configuration saved.',
      });
    } catch (error: any) {
      console.error('[SmtpConfig] Save failed:', error?.message);
      res.status(500).json({ error: error?.message || 'Failed to save the email configuration' });
    }
  }
);

// PATCH /:id/default — Make this the configuration all system email uses
router.patch('/:id/default', async (req: Request, res: Response) => {
  try {
    const { SmtpConfig } = getModels();
    const config = await SmtpConfig.findById(req.params.id);
    if (!config) {
      res.status(404).json({ error: 'Email configuration not found' });
      return;
    }
    if (!config.isActive) {
      res.status(400).json({ error: 'Enable this configuration before making it the default.' });
      return;
    }
    const missing = missingRequiredFields(config);
    if (missing.length > 0) {
      res.status(400).json({ error: `This configuration is incomplete — missing: ${missing.join(', ')}.` });
      return;
    }

    await promoteToDefault(String(config._id));
    invalidateMailerCache();

    console.log(`[SmtpConfig] "${config.name}" set as default by ${req.user?.email}`);
    res.json({ message: `"${config.name || config.host}" is now used for all system emails.` });
  } catch (error: any) {
    console.error('[SmtpConfig] Set default failed:', error?.message);
    res.status(500).json({ error: 'Failed to set the default configuration' });
  }
});

// DELETE /:id — Remove a configuration
router.delete('/:id', async (req: Request, res: Response) => {
  try {
    const { SmtpConfig } = getModels();
    const config = await SmtpConfig.findById(req.params.id);
    if (!config) {
      res.status(404).json({ error: 'Email configuration not found' });
      return;
    }

    const name = config.name || config.host;
    await SmtpConfig.deleteOne({ _id: config._id });

    // Deleting the default hands the role to another enabled configuration.
    await ensureDefaultExists();
    invalidateMailerCache();

    console.log(`[SmtpConfig] Deleted "${name}" by ${req.user?.email}`);
    res.json({ message: `"${name}" deleted.` });
  } catch (error: any) {
    console.error('[SmtpConfig] Delete failed:', error?.message);
    res.status(500).json({ error: 'Failed to delete the email configuration' });
  }
});

// ============================================
// Testing
// ============================================

/**
 * Settings to test: the stored configuration, overlaid with anything the admin
 * has typed but not yet saved (the password is only ever taken from the request
 * when newly entered, since the UI never holds it).
 */
async function settingsForTest(id: string | undefined, reqBody: any): Promise<SmtpSettings | null> {
  const stored = id ? await loadSmtpSettingsById(id) : await loadSmtpSettings();
  if (!stored && !reqBody?.host) return null;

  const preset = getSmtpProviderPreset(reqBody?.provider || stored?.provider || '');

  // A password typed into the form is usable as it stands; only the stored one
  // can be unreadable, and typing a new one is exactly how that gets fixed.
  let password = stored?.password || '';
  let passwordUnreadable = stored?.passwordUnreadable === true;
  if (typeof reqBody?.password === 'string' && reqBody.password.trim() !== '') {
    password = reqBody.password;
    passwordUnreadable = false;
  }

  return {
    id: stored?.id,
    ...(passwordUnreadable ? { passwordUnreadable: true } : {}),
    name: reqBody?.name ?? stored?.name ?? '',
    provider: reqBody?.provider ?? stored?.provider ?? 'custom',
    host: String(reqBody?.host ?? stored?.host ?? preset?.host ?? '').trim(),
    port: Number(reqBody?.port ?? stored?.port ?? preset?.port ?? 587),
    encryption: reqBody?.encryption ?? stored?.encryption ?? preset?.encryption ?? 'tls',
    username: String(reqBody?.username ?? stored?.username ?? '').trim(),
    password,
    senderName: reqBody?.senderName ?? stored?.senderName ?? '',
    senderEmail: String(reqBody?.senderEmail ?? stored?.senderEmail ?? '').trim(),
    replyToEmail: reqBody?.replyToEmail ?? stored?.replyToEmail ?? '',
    // Testing is an explicit action, so the enabled switch does not gate it.
    isActive: true,
    rejectUnauthorized: reqBody?.rejectUnauthorized ?? stored?.rejectUnauthorized ?? true,
  };
}

/** Record the outcome on the configuration so it survives a reload. */
async function recordTestResult(id: string | undefined, success: boolean, message: string): Promise<void> {
  if (!id) return;
  try {
    const { SmtpConfig } = getModels();
    const config = await SmtpConfig.findById(id);
    if (!config) return;
    config.lastTestedAt = new Date();
    config.lastTestSuccess = success;
    config.lastTestMessage = message;
    await config.save();
  } catch {
    // Diagnostics only — never fail the test because it couldn't be recorded.
  }
}

// POST /:id/test — Verify one configuration's connection without sending
router.post('/:id/test', async (req: Request, res: Response) => {
  try {
    const settings = await settingsForTest(req.params.id, req.body);
    if (!settings) {
      res.status(404).json({ success: false, message: 'Email configuration not found' });
      return;
    }
    const result = await verifySmtpConnection(settings);
    await recordTestResult(req.params.id, result.success, result.message);
    res.json(result);
  } catch (error: any) {
    console.error('[SmtpConfig] Connection test failed:', error?.message);
    res.status(500).json({ success: false, message: error?.message || 'Connection test failed' });
  }
});

// POST /test — Verify unsaved settings (used by the "add" form before saving)
router.post('/test', async (req: Request, res: Response) => {
  try {
    const settings = await settingsForTest(undefined, req.body);
    if (!settings?.host) {
      res.status(400).json({ success: false, message: 'Enter an SMTP host before testing.' });
      return;
    }
    const result = await verifySmtpConnection(settings);
    res.json(result);
  } catch (error: any) {
    console.error('[SmtpConfig] Connection test failed:', error?.message);
    res.status(500).json({ success: false, message: error?.message || 'Connection test failed' });
  }
});

// POST /:id/send-test — Send a real email through one specific configuration
router.post(
  '/:id/send-test',
  [body('to').trim().isEmail().withMessage('A valid recipient email is required')],
  async (req: Request, res: Response) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        res.status(400).json({ success: false, message: errors.array()[0]?.msg || 'Invalid recipient' });
        return;
      }

      if (!isMailerDependencyAvailable()) {
        res.status(400).json({ success: false, message: MAILER_DEPENDENCY_MESSAGE });
        return;
      }

      const settings = await loadSmtpSettingsById(req.params.id);
      if (!settings) {
        res.status(404).json({ success: false, message: 'Email configuration not found' });
        return;
      }
      if (!settings.host || !settings.senderEmail) {
        res.status(400).json({ success: false, message: 'This configuration is incomplete — set the host and sender email first.' });
        return;
      }

      const to = String(req.body.to).trim();
      const html = `
        <div style="font-family:Arial,Helvetica,sans-serif;line-height:1.6;color:#111">
          <h2 style="margin:0 0 12px">SMTP is working</h2>
          <p>This test email was sent from your MengoEngine Email Configuration.</p>
          <table cellpadding="6" style="border-collapse:collapse;font-size:14px">
            <tr><td><strong>Configuration</strong></td><td>${settings.name || '(unnamed)'}</td></tr>
            <tr><td><strong>Provider</strong></td><td>${settings.provider}</td></tr>
            <tr><td><strong>Host</strong></td><td>${settings.host}:${settings.port}</td></tr>
            <tr><td><strong>Encryption</strong></td><td>${settings.encryption.toUpperCase()}</td></tr>
            <tr><td><strong>From</strong></td><td>${settings.senderName} &lt;${settings.senderEmail}&gt;</td></tr>
          </table>
        </div>
      `;

      // Send through THIS configuration, not the default — the whole point of
      // the button is verifying a provider before promoting it.
      const transport = buildTransport(settings);
      if (!transport) {
        res.status(400).json({ success: false, message: MAILER_DEPENDENCY_MESSAGE });
        return;
      }

      try {
        const info = await transport.sendMail({
          from: settings.senderName ? `"${settings.senderName.replace(/"/g, '')}" <${settings.senderEmail}>` : settings.senderEmail,
          to,
          replyTo: settings.replyToEmail || settings.senderEmail,
          subject: 'MengoEngine SMTP test email',
          html,
        });
        const message = `Test email sent to ${to}. Check the inbox (and the spam folder).`;
        await recordTestResult(req.params.id, true, message);
        res.json({ success: true, message, messageId: info?.messageId });
      } catch (sendErr: any) {
        const message = sendErr?.message || 'Failed to send the test email';
        await recordTestResult(req.params.id, false, message);
        res.status(502).json({ success: false, message });
      } finally {
        try { transport.close?.(); } catch { /* nothing to do */ }
      }
    } catch (error: any) {
      console.error('[SmtpConfig] Test send failed:', error?.message);
      res.status(500).json({ success: false, message: error?.message || 'Failed to send the test email' });
    }
  }
);

// POST /send-test — Send through whichever configuration is currently default
router.post(
  '/send-test',
  [body('to').trim().isEmail().withMessage('A valid recipient email is required')],
  async (req: Request, res: Response) => {
    try {
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        res.status(400).json({ success: false, message: errors.array()[0]?.msg || 'Invalid recipient' });
        return;
      }

      const active = await loadSmtpSettings();
      if (!active) {
        res.status(400).json({ success: false, message: 'No enabled email configuration. Add one and enable it first.' });
        return;
      }

      const to = String(req.body.to).trim();
      const result = await sendSystemEmail({
        to,
        subject: 'MengoEngine SMTP test email',
        html: `<p>This test email was sent through <strong>${active.name || active.provider}</strong> (${active.host}), the configuration all system emails currently use.</p>`,
      });

      const message = result.success
        ? `Test email sent to ${to} via ${active.name || active.provider}.`
        : result.error || 'Failed to send the test email';

      await recordTestResult(active.id, result.success, message);
      res.status(result.success ? 200 : 502).json({ success: result.success, message, messageId: result.messageId });
    } catch (error: any) {
      console.error('[SmtpConfig] Test send failed:', error?.message);
      res.status(500).json({ success: false, message: error?.message || 'Failed to send the test email' });
    }
  }
);

export default router;
