import {
  MarketingProviderError,
  registerMarketingAdapter,
  sendCampaignThroughCompliance,
  type Campaign,
  type CampaignStats,
  type ComplianceStore,
  type MarketingAdapter,
  type MarketingContact,
  type MarketingList,
  type SendCampaignOpts,
  type SendCampaignResult,
  type SuppressionEntry,
} from './index.js'

const BREVO_BASE = 'https://api.brevo.com/v3'

// Maps Brevo's structured blocked-contact reason.code to our SuppressionEntry reason.
// Keyed on reason.code only — Brevo marks reason.message "not safe for comparison".
function mapBrevoSuppressionReason(code?: string): SuppressionEntry['reason'] {
  const c = code?.toLowerCase() ?? ''
  if (c.includes('bounce')) return 'bounce'
  if (c.includes('spam') || c.includes('complaint')) return 'complaint'
  if (c.includes('unsubscrib')) return 'unsubscribe'
  return 'manual'
}

export type BrevoCreds = {
  apiKey: string
  fetchImpl?: typeof fetch
  senderEmail: string
  senderName?: string
  complianceStore: ComplianceStore
}

export type BrevoDoiCreds = Pick<BrevoCreds, 'apiKey' | 'fetchImpl'>

export type BrevoDoiContactOpts = {
  listId: number
  templateId: number
  redirectionUrl: string
}

export type BrevoUpsertContactOpts = {
  updateEnabled?: boolean
}

export type BrevoDoiAdapter = {
  upsertContact(
    contact: MarketingContact,
    listId?: string,
    opts?: BrevoUpsertContactOpts,
  ): Promise<void>
  createDoiContact(contact: MarketingContact, opts: BrevoDoiContactOpts): Promise<void>
}

export type BrevoAdapter = Omit<MarketingAdapter, 'upsertContact'> &
  BrevoDoiAdapter & {
    createSegment(def: { name: string; listId: number }): Promise<{ id: number; name: string }>
    scheduleCampaign(campaignId: string | number, scheduledAt: string): Promise<void>
    createAutomation(def: {
      name: string
      listId: string | number
    }): Promise<{ id: number; name: string }>
  }

type BrevoErrorBody = {
  message?: string
  code?: string
}

async function brevoFetch(
  creds: BrevoDoiCreds,
  path: string,
  init?: RequestInit,
): Promise<Response> {
  const fetchImpl = creds.fetchImpl ?? fetch
  try {
    return await fetchImpl(`${BREVO_BASE}${path}`, {
      ...init,
      headers: {
        'api-key': creds.apiKey,
        accept: 'application/json',
        'content-type': 'application/json',
        ...(init?.headers ?? {}),
      },
    })
  } catch (err) {
    throw new MarketingProviderError(
      err instanceof Error ? err.message : 'brevo fetch failed',
      'brevo',
      true,
      err,
    )
  }
}

async function parseBrevoError(response: Response): Promise<MarketingProviderError> {
  const body = (await response.json().catch(() => null)) as BrevoErrorBody | null
  return new MarketingProviderError(
    body?.message ?? `brevo request failed (${response.status})`,
    'brevo',
    response.status >= 500,
    body,
  )
}

export function makeBrevoAdapter(creds: BrevoCreds): BrevoAdapter
export function makeBrevoAdapter(creds: BrevoDoiCreds): BrevoDoiAdapter
export function makeBrevoAdapter(creds: BrevoCreds | BrevoDoiCreds): BrevoAdapter {
  const fullCreds = creds as BrevoCreds
  const campaignRecipients = new Map<string, string[]>()

  const adapter = {
    name: 'brevo',
    supports: {
      segments: true,
      scheduling: true,
      automation: true,
    } as const,

    async upsertContact(
      contact: MarketingContact,
      listId?: string,
      opts?: BrevoUpsertContactOpts,
    ): Promise<void> {
      const body: Record<string, unknown> = {
        email: contact.email,
        attributes: contact.attributes,
        updateEnabled: opts?.updateEnabled ?? true,
      }
      if (listId) {
        body.listIds = [Number.parseInt(listId, 10)]
      }

      const response = await brevoFetch(creds, '/contacts', {
        method: 'POST',
        body: JSON.stringify(body),
      })
      if (!response.ok) throw await parseBrevoError(response)
    },

    async createDoiContact(
      contact: MarketingContact,
      opts: BrevoDoiContactOpts,
    ): Promise<void> {
      const response = await brevoFetch(creds, '/contacts/doubleOptinConfirmation', {
        method: 'POST',
        body: JSON.stringify({
          email: contact.email,
          attributes: contact.attributes,
          includeListIds: [opts.listId],
          templateId: opts.templateId,
          redirectionUrl: opts.redirectionUrl,
        }),
      })
      if (!response.ok) throw await parseBrevoError(response)
    },

    async removeContact(email: string): Promise<void> {
      const response = await brevoFetch(creds, `/contacts/${encodeURIComponent(email)}`, {
        method: 'DELETE',
      })
      if (!response.ok && response.status !== 404) throw await parseBrevoError(response)
    },

    async tagContact(email: string, tag: string): Promise<void> {
      const response = await brevoFetch(creds, `/contacts/${encodeURIComponent(email)}`, {
        method: 'PUT',
        body: JSON.stringify({ attributes: { TAG: tag } }),
      })
      if (!response.ok) throw await parseBrevoError(response)
    },

    async createList(name: string): Promise<MarketingList> {
      const response = await brevoFetch(creds, '/contacts/lists', {
        method: 'POST',
        body: JSON.stringify({ name, folderId: 1 }),
      })
      if (!response.ok) throw await parseBrevoError(response)
      const payload = (await response.json()) as { id: number }
      return { id: String(payload.id), name }
    },

    async lists(): Promise<MarketingList[]> {
      const response = await brevoFetch(creds, '/contacts/lists?limit=50', { method: 'GET' })
      if (!response.ok) throw await parseBrevoError(response)
      const payload = (await response.json()) as { lists?: Array<{ id: number; name: string }> }
      return (payload.lists ?? []).map((list) => ({ id: String(list.id), name: list.name }))
    },

    async createCampaign(campaign: Omit<Campaign, 'id'>): Promise<Campaign> {
      const response = await brevoFetch(creds, '/emailCampaigns', {
        method: 'POST',
        body: JSON.stringify({
          name: campaign.name,
          subject: campaign.subject,
          htmlContent: campaign.html,
          textContent: campaign.text,
          recipients: { listIds: campaign.listIds.map((id) => Number.parseInt(id, 10)) },
          sender: { email: fullCreds.senderEmail, name: fullCreds.senderName },
        }),
      })
      if (!response.ok) throw await parseBrevoError(response)
      const payload = (await response.json()) as { id: number }
      const created: Campaign = { ...campaign, id: String(payload.id) }
      campaignRecipients.set(created.id, [])
      return created
    },

    async sendCampaign(campaignId: string, opts: SendCampaignOpts): Promise<SendCampaignResult> {
      const recipients = campaignRecipients.get(campaignId) ?? []
      if (recipients.length === 0) {
        const listResponse = await brevoFetch(
          creds,
          `/emailCampaigns/${encodeURIComponent(campaignId)}`,
          { method: 'GET' },
        )
        if (listResponse.ok) {
          const payload = (await listResponse.json()) as {
            recipients?: { lists?: number[] }
          }
          const listIds = payload.recipients?.lists ?? []
          const collected: string[] = []
          for (const listId of listIds) {
            const contactsResponse = await brevoFetch(
              creds,
              `/contacts/lists/${listId}/contacts?limit=50`,
              { method: 'GET' },
            )
            if (!contactsResponse.ok) continue
            const contactsPayload = (await contactsResponse.json()) as {
              contacts?: Array<{ email: string }>
            }
            for (const contact of contactsPayload.contacts ?? []) {
              collected.push(contact.email)
            }
          }
          campaignRecipients.set(campaignId, collected)
        }
      }

      const resolvedRecipients = campaignRecipients.get(campaignId) ?? []
      const campaign: Campaign = {
        id: campaignId,
        name: campaignId,
        subject: 'campaign',
        html: '<p>campaign</p>',
        listIds: [],
        region: opts.legal.region,
      }

      const complianceResult = await sendCampaignThroughCompliance(
        fullCreds.complianceStore,
        {
          campaignId,
          campaign,
          legal: opts.legal,
          recipients: resolvedRecipients,
          trackingBaseUrl: opts.trackingBaseUrl,
        },
        async () => {
          // Defense-in-depth pre-filter only — Brevo enforces at its send boundary.
        },
      )

      if (complianceResult.sent.length === 0) {
        return complianceResult
      }

      const response = await brevoFetch(
        creds,
        `/emailCampaigns/${encodeURIComponent(campaignId)}/sendNow`,
        { method: 'POST' },
      )
      if (!response.ok) throw await parseBrevoError(response)

      return complianceResult
    },

    async campaignStats(campaignId: string): Promise<CampaignStats> {
      // Brevo has no /emailCampaigns/{id}/statistics subpath; stats come from the
      // base campaign GET under nested statistics.globalStats (verified vs Brevo OpenAPI).
      const response = await brevoFetch(
        creds,
        `/emailCampaigns/${encodeURIComponent(campaignId)}?statistics=globalStats&excludeHtmlContent=true`,
        { method: 'GET' },
      )
      if (!response.ok) throw await parseBrevoError(response)
      const payload = (await response.json()) as {
        statistics?: {
          globalStats?: {
            uniqueViews?: number
            uniqueClicks?: number
            hardBounces?: number
            unsubscriptions?: number
          }
        }
      }
      const stats = payload.statistics?.globalStats
      return {
        opens: stats?.uniqueViews ?? 0,
        clicks: stats?.uniqueClicks ?? 0,
        bounces: stats?.hardBounces ?? 0,
        unsubs: stats?.unsubscriptions ?? 0,
      }
    },

    async syncSuppression(): Promise<SuppressionEntry[]> {
      // limit=50 is a single page; full pagination is deferred/out-of-scope (plan Task 4).
      const response = await brevoFetch(creds, '/smtp/blockedContacts?limit=50', {
        method: 'GET',
      })
      if (!response.ok) throw await parseBrevoError(response)
      const payload = (await response.json()) as {
        contacts?: Array<{
          email: string
          blockedAt?: string
          reason?: { code?: string; message?: string }
        }>
      }
      return (payload.contacts ?? []).map((entry) => ({
        email: entry.email,
        reason: mapBrevoSuppressionReason(entry.reason?.code),
        suppressedAt: entry.blockedAt ?? '',
      }))
    },

    async createSegment(def: { name: string; listId: number }) {
      const response = await brevoFetch(creds, '/contacts/segments', {
        method: 'POST',
        body: JSON.stringify({ segmentName: def.name, listIds: [def.listId] }),
      })
      if (!response.ok) throw await parseBrevoError(response)
      const payload = (await response.json()) as { id: number }
      return { id: payload.id, name: def.name }
    },

    async scheduleCampaign(campaignId: string | number, scheduledAt: string): Promise<void> {
      const id = typeof campaignId === 'string' ? campaignId : String(campaignId)
      const response = await brevoFetch(creds, `/emailCampaigns/${id}`, {
        method: 'PUT',
        body: JSON.stringify({ scheduledAt }),
      })
      if (!response.ok) throw await parseBrevoError(response)
    },

    async createAutomation(def: { name: string; listId: string | number }) {
      const listId = typeof def.listId === 'string' ? Number.parseInt(def.listId, 10) : def.listId
      const response = await brevoFetch(creds, '/automations', {
        method: 'POST',
        body: JSON.stringify({ name: def.name, listIds: [listId] }),
      })
      if (!response.ok) throw await parseBrevoError(response)
      const payload = (await response.json()) as { id: number }
      return { id: payload.id, name: def.name }
    },
  }

  return adapter
}

registerMarketingAdapter('brevo', (creds) => makeBrevoAdapter(creds as BrevoCreds))
