import { query, queryOne } from '../db';
import { logger } from '../logger';
import { createTransporter } from '../mailer';
import { compileTemplate, TemplateData } from './handlebars';
import { rewriteLinks } from './linkRewriter';
import { withSmtpRetry } from './smtpRetry';

const BASE_URL = process.env.APP_BASE_URL || 'http://localhost:3002';

/** RFC 8058 one-click unsubscribe (paired with POST /track/unsubscribe/:token). */
export function listUnsubscribeMailHeaders(unsubscribeUrl: string): Record<string, string> {
  return {
    'List-Unsubscribe': `<${unsubscribeUrl}>`,
    'List-Unsubscribe-Post': 'List-Unsubscribe=One-Click',
  };
}

interface Campaign {
  id: string;
  name: string;
  subject: string;
  from_name: string;
  from_email: string;
  reply_to: string | null;
  list_id: string;
  compiled_html: string;
}

interface Contact {
  id: string;
  email: string;
  first_name: string | null;
  last_name: string | null;
  custom_fields: Record<string, unknown>;
}

interface Recipient {
  id: string;
  contact_id: string;
  track_token: string;
}

/** Shared finalizer after bulk or enriched send loops. */
export async function finalizeCampaignStatus(campaignId: string): Promise<void> {
  const rows = await query<{ status: string; count: string }>(
    `SELECT status, COUNT(*)::text as count FROM campaign_recipients WHERE campaign_id = $1 GROUP BY status`,
    [campaignId]
  );
  const counts: Record<string, number> = {};
  for (const r of rows) counts[r.status] = parseInt(r.count, 10);
  const sent = counts.sent || 0;
  const failed = counts.failed || 0;
  const pending = counts.pending || 0;

  if (pending > 0) {
    await query(`UPDATE campaigns SET status = 'paused', updated_at = now() WHERE id = $1`, [campaignId]);
    logger.warn({ campaignId, pending }, 'Campaign send paused with pending recipients');
    return;
  }
  if (sent === 0 && failed > 0) {
    await query(`UPDATE campaigns SET status = 'failed', updated_at = now() WHERE id = $1`, [campaignId]);
    return;
  }
  await query(
    `UPDATE campaigns SET status = 'sent', sent_at = now(), updated_at = now() WHERE id = $1`,
    [campaignId]
  );
}

export async function sendCampaign(campaignId: string): Promise<void> {
  const campaign = await queryOne<Campaign>(`
    SELECT c.id, c.name, c.subject, c.from_name, c.from_email, c.reply_to, c.list_id,
           t.compiled_html
    FROM campaigns c
    LEFT JOIN templates t ON t.id = c.template_id
    WHERE c.id = $1
  `, [campaignId]);

  if (!campaign) throw new Error('Campaign not found');
  if (!campaign.compiled_html) throw new Error('Campaign has no compiled template');

  const contacts = await query<Contact>(`
    SELECT c.id, c.email, c.first_name, c.last_name, c.custom_fields
    FROM list_contacts lc
    JOIN contacts c ON c.id = lc.contact_id
    WHERE lc.list_id = $1 AND c.subscribed = true
  `, [campaign.list_id]);

  if (contacts.length === 0) {
    await query(`UPDATE campaigns SET status = 'paused', updated_at = now() WHERE id = $1`, [campaignId]);
    logger.warn({ campaignId }, 'Campaign send skipped: no subscribed contacts on list');
    return;
  }

  for (const contact of contacts) {
    await query(
      `INSERT INTO campaign_recipients (campaign_id, contact_id)
       VALUES ($1, $2)
       ON CONFLICT (campaign_id, contact_id) DO NOTHING`,
      [campaignId, contact.id]
    );
  }

  await query(
    `UPDATE campaigns SET status = 'sending', updated_at = now() WHERE id = $1`,
    [campaignId]
  );

  const companySetting = await queryOne<{ value: string }>(
    'SELECT value FROM app_settings WHERE key = $1', ['company_name']
  );
  const companyName = (companySetting?.value as string) || 'My Company';

  const transporter = await createTransporter();
  const BATCH = 50;

  try {
    for (let i = 0; i < contacts.length; i += BATCH) {
      const batch = contacts.slice(i, i + BATCH);

      for (const contact of batch) {
        const recipient = await queryOne<Recipient>(
          `SELECT id, contact_id, track_token FROM campaign_recipients
           WHERE campaign_id = $1 AND contact_id = $2 AND status = 'pending'`,
          [campaignId, contact.id]
        );

        if (!recipient) continue;

        try {
          const unsubscribeUrl = `${BASE_URL}/track/unsubscribe/${recipient.track_token}`;

          const templateData: TemplateData = {
            ...contact.custom_fields,
            first_name: contact.first_name,
            last_name: contact.last_name,
            email: contact.email,
            unsubscribe_url: unsubscribeUrl,
            company_name: companyName,
          };

          let html = compileTemplate(campaign.compiled_html, templateData);
          html = await rewriteLinks(html, campaignId, recipient.track_token);

          const pixel = `<img src="${BASE_URL}/track/open/${recipient.track_token}.gif" width="1" height="1" alt="" style="display:none;border:0;" />`;
          html = html.replace(/<\/body>/i, `${pixel}</body>`);

          const subject = compileTemplate(campaign.subject, templateData);
          await withSmtpRetry(() =>
            transporter.sendMail({
              from: `"${campaign.from_name}" <${campaign.from_email}>`,
              replyTo: campaign.reply_to || undefined,
              to: contact.email,
              subject,
              html,
              headers: listUnsubscribeMailHeaders(unsubscribeUrl),
            })
          );

          await query(
            `UPDATE campaign_recipients SET status = 'sent', sent_at = now() WHERE id = $1`,
            [recipient.id]
          );

          await query(
            `INSERT INTO email_events (campaign_id, contact_id, type) VALUES ($1, $2, 'sent')`,
            [campaignId, contact.id]
          );
        } catch (err) {
          logger.error({ err, email: contact.email, campaignId }, 'Failed to send campaign email');
          await query(
            `UPDATE campaign_recipients SET status = 'failed' WHERE id = $1`,
            [recipient.id]
          );
        }
      }

      if (i + BATCH < contacts.length) {
        await new Promise(r => setTimeout(r, 100));
      }
    }
  } finally {
    await finalizeCampaignStatus(campaignId);
  }
}

export async function sendTestEmail(campaignId: string, testEmail: string): Promise<void> {
  const campaign = await queryOne<Campaign>(`
    SELECT c.id, c.name, c.subject, c.from_name, c.from_email, c.reply_to,
           t.compiled_html
    FROM campaigns c
    LEFT JOIN templates t ON t.id = c.template_id
    WHERE c.id = $1
  `, [campaignId]);

  if (!campaign) throw new Error('Campaign not found');
  if (!campaign.compiled_html) throw new Error('Campaign has no compiled template');

  const templateData: TemplateData = {
    first_name: 'Test',
    last_name: 'User',
    email: testEmail,
    unsubscribe_url: '#unsubscribe',
    company_name: 'My Company',
  };

  const html = compileTemplate(campaign.compiled_html, templateData);
  const subject = compileTemplate(campaign.subject, templateData);
  const transporter = await createTransporter();

  await transporter.sendMail({
    from: `"${campaign.from_name}" <${campaign.from_email}>`,
    to: testEmail,
    subject: `[TEST] ${subject}`,
    html,
  });
}
