// BulkMailer: abstraction for sending bulk/broadcast campaigns.
// Currently only BrevoMailer is implemented (Brevo/Sendinblue API).

import {
  CampaignSendProviderError,
  type CampaignReconciliation,
} from '@/server/workflows/campaign-send.js';
import { captureCaught } from '@/server/observability/capture.server.js';

export interface BulkMailerContact {
  email: string;
  firstName?: string;
  lastName?: string;
  attributes?: Record<string, string | number | boolean>;
}

export interface BulkCampaign {
  name: string;
  subject: string;
  htmlContent: string;
  sender: { name: string; email: string };
  listIds: number[]; // Brevo list IDs to send to
  scheduledAt?: string; // ISO string; if undefined, send immediately
}

export interface BulkMailer {
  /** Upsert a contact into the mailing list */
  upsertContact(contact: BulkMailerContact, listId: number): Promise<void>;
  /** Remove a contact from all lists */
  removeContact(email: string): Promise<void>;
  /** Create and optionally schedule a campaign */
  createCampaign(campaign: BulkCampaign): Promise<{ campaignId: number }>;
  /** Send a campaign immediately (if already created) */
  sendCampaign(campaignId: number): Promise<void>;
  /** Read provider lifecycle status for ambiguous-send reconciliation. */
  reconcileCampaign(campaignId: number): Promise<CampaignReconciliation>;
  /** Get campaign stats */
  getCampaignStats(campaignId: number): Promise<CampaignStats>;
}

export interface CampaignStats {
  campaignId: number;
  delivered: number;
  opens: number;
  clicks: number;
  unsubscribes: number;
  bounces: number;
}

class BrevoHttpError extends Error {
  constructor(
    message: string,
    readonly status: number,
  ) {
    super(message);
  }
}

// ─── BrevoMailer ──────────────────────────────────────────────────────────────

export class BrevoMailer implements BulkMailer {
  private readonly apiKey: string;
  private readonly baseUrl = 'https://api.brevo.com/v3';

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  private async brevoFetch<T>(path: string, opts: { method: string; body?: unknown }): Promise<T> {
    const res = await fetch(`${this.baseUrl}${path}`, {
      method: opts.method,
      headers: {
        'api-key': this.apiKey,
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
      body: opts.body ? JSON.stringify(opts.body) : undefined,
    });
    if (!res.ok) {
      let text = '';
      try {
        text = await res.text();
      } catch (err) {
        captureCaught(err, {
          scope: 'server.email.bulk-mailer.error-body',
          severity: 'info',
        });
      }
      throw new BrevoHttpError(`Brevo ${opts.method} ${path} → ${res.status}: ${text}`, res.status);
    }
    if (res.status === 204) return undefined as T;
    return res.json() as Promise<T>;
  }

  async upsertContact(contact: BulkMailerContact, listId: number): Promise<void> {
    await this.brevoFetch('/contacts', {
      method: 'POST',
      body: {
        email: contact.email,
        attributes: {
          FIRSTNAME: contact.firstName,
          LASTNAME: contact.lastName,
          ...contact.attributes,
        },
        listIds: [listId],
        updateEnabled: true,
      },
    });
  }

  async removeContact(email: string): Promise<void> {
    // Brevo: update contact to remove from all lists, then delete (soft)
    // Using unblacklist=false; contact stays in system but unsubscribed
    await this.brevoFetch(`/contacts/${encodeURIComponent(email)}`, {
      method: 'PUT',
      body: { emailBlacklisted: true },
    });
  }

  async createCampaign(campaign: BulkCampaign): Promise<{ campaignId: number }> {
    const body: Record<string, unknown> = {
      name: campaign.name,
      subject: campaign.subject,
      htmlContent: campaign.htmlContent,
      sender: campaign.sender,
      recipients: { listIds: campaign.listIds },
    };
    if (campaign.scheduledAt) body.scheduledAt = campaign.scheduledAt;
    const res = await this.brevoFetch<{ id: number }>('/emailCampaigns', {
      method: 'POST',
      body,
    });
    return { campaignId: res.id };
  }

  async sendCampaign(campaignId: number): Promise<void> {
    try {
      await this.brevoFetch(`/emailCampaigns/${campaignId}/sendNow`, {
        method: 'POST',
      });
    } catch (error) {
      const definitive =
        error instanceof BrevoHttpError &&
        error.status >= 400 &&
        error.status < 500 &&
        error.status !== 408 &&
        error.status !== 429;
      throw new CampaignSendProviderError(
        String(error),
        definitive ? 'definitively_not_accepted' : 'unknown',
      );
    }
  }

  async reconcileCampaign(campaignId: number) {
    const campaign = await this.brevoFetch<{ status: string }>(`/emailCampaigns/${campaignId}`, {
      method: 'GET',
    });
    if (campaign.status === 'sent' || campaign.status === 'queued') return 'accepted' as const;
    return 'unknown' as const;
  }

  async getCampaignStats(campaignId: number): Promise<CampaignStats> {
    const res = await this.brevoFetch<{
      id: number;
      statistics: {
        globalStats: {
          delivered: number;
          uniqueViews: number;
          trackableClicks: number;
          unsubscriptions: number;
          hardBounces: number;
          softBounces: number;
        };
      };
    }>(`/emailCampaigns/${campaignId}`, { method: 'GET' });
    const s = res.statistics.globalStats;
    return {
      campaignId,
      delivered: s.delivered ?? 0,
      opens: s.uniqueViews ?? 0,
      clicks: s.trackableClicks ?? 0,
      unsubscribes: s.unsubscriptions ?? 0,
      bounces: (s.hardBounces ?? 0) + (s.softBounces ?? 0),
    };
  }
}
