import { describe, expect, it, vi } from 'vitest'
import type { MailMessage } from '@platform-modules/mail'
import {
  confirmDoubleOptIn,
  createMemoryComplianceStore,
  type CampaignLegalContext,
  type ComplianceStore,
} from './compliance.js'
import type {
  Campaign,
  CampaignStats,
  MarketingContact,
  MarketingList,
  SuppressionEntry,
} from './index.js'
import { makeSelfManagedAdapter, type SelfManagedStore } from './self-managed.js'

const legal: CampaignLegalContext = {
  region: 'US',
  oneClickUnsubscribeUrl: 'https://example.com/unsub',
  legal: {
    spf: true,
    dkim: true,
    dmarc: true,
    physicalPostalAddress: '1 Market St',
    advertisingLabel: 'Ad',
  },
}

function createFakeStore(initial?: {
  campaign?: Campaign
  recipients?: string[]
  stats?: CampaignStats
}): SelfManagedStore {
  const lists: MarketingList[] = []
  const segments: Array<{ id: string; name: string; listId: string; filter: object }> = []
  const stats: CampaignStats = initial?.stats ?? {
    opens: 0,
    clicks: 0,
    bounces: 0,
    unsubs: 0,
  }

  const campaign =
    initial?.campaign ??
    ({
      id: 'camp-1',
      name: 'Launch',
      subject: 'Hello',
      html: '<p>Hi</p>',
      listIds: ['list-1'],
    } satisfies Campaign)

  return {
    async upsertContact(_contact: MarketingContact) {},
    async removeContact() {},
    async tagContact() {},
    async createList(name: string) {
      const list = { id: `list_${lists.length + 1}`, name }
      lists.push(list)
      return list
    },
    async lists() {
      return lists
    },
    async saveCampaign() {},
    async getCampaign(id: string) {
      return id === campaign.id ? campaign : null
    },
    async getListRecipients() {
      return initial?.recipients ?? ['ok@example.com', 'blocked@example.com']
    },
    async recordOpen(_campaignId, _email) {
      stats.opens += 1
    },
    async recordClick(_campaignId, _email) {
      stats.clicks += 1
    },
    async getStats() {
      return stats
    },
    async createSegment(def) {
      const segment = { id: `seg_${segments.length + 1}`, ...def }
      segments.push(segment)
      return segment
    },
    async listSegments(listId) {
      return listId ? segments.filter((s) => s.listId === listId) : segments
    },
    async scheduleCampaign() {},
  }
}

describe('makeSelfManagedAdapter', () => {
  it('routes each eligible recipient through mail.send and the compliance gate', async () => {
    const complianceStore = createMemoryComplianceStore({
      suppressed: [
        {
          email: 'blocked@example.com',
          reason: 'unsubscribe',
          suppressedAt: '2026-01-01T00:00:00.000Z',
        },
      ],
    })
    await confirmDoubleOptIn(complianceStore, 'ok@example.com', 'US')

    const send = vi.fn(async (_msg: MailMessage) => ({ id: 'msg-1', provider: 'fake' }))
    const store = createFakeStore()

    const adapter = makeSelfManagedAdapter({
      from: 'news@example.com',
      mail: { send },
      store,
      complianceStore,
    })

    const result = await adapter.sendCampaign('camp-1', {
      trackingBaseUrl: 'https://track.example',
      legal,
    })

    expect(result.sent).toEqual(['ok@example.com'])
    expect(result.refused).toEqual([
      expect.objectContaining({ email: 'blocked@example.com', code: 'suppressed' }),
    ])
    expect(send).toHaveBeenCalledTimes(1)
    expect(send.mock.calls[0]?.[0]).toEqual(
      expect.objectContaining({
        to: 'ok@example.com',
        headers: expect.objectContaining({
          'List-Unsubscribe': `<${legal.oneClickUnsubscribeUrl}>`,
        }),
      }),
    )
  })

  it('updates host-side stats for opens and clicks', async () => {
    const store = createFakeStore()
    const adapter = makeSelfManagedAdapter({
      from: 'news@example.com',
      mail: { send: vi.fn(async () => ({ id: 'm1', provider: 'fake' })) },
      store,
      complianceStore: createMemoryComplianceStore(),
    })

    await adapter.recordOpen('camp-1', 'reader@example.com')
    await adapter.recordClick('camp-1', 'reader@example.com', 'https://shop.example')

    const stats = await adapter.campaignStats('camp-1')
    expect(stats.opens).toBe(1)
    expect(stats.clicks).toBe(1)
  })

  it('syncSuppression returns entries from the compliance store mirror', async () => {
    const complianceStore = createMemoryComplianceStore({
      suppressed: [
        {
          email: 'gone@example.com',
          reason: 'unsubscribe',
          suppressedAt: '2026-02-01T00:00:00.000Z',
        },
      ],
    })

    const adapter = makeSelfManagedAdapter({
      from: 'news@example.com',
      mail: { send: vi.fn() },
      store: createFakeStore({ recipients: [] }),
      complianceStore,
    })

    const entries = await adapter.syncSuppression()
    expect(entries).toEqual([
      expect.objectContaining({ email: 'gone@example.com', reason: 'unsubscribe' }),
    ])
  })

  it('syncSuppression enumerates via listSuppressed for a non-memory store (no `suppressed` property)', async () => {
    const known: SuppressionEntry[] = [
      { email: 'a@example.com', reason: 'unsubscribe', suppressedAt: '2026-03-01T00:00:00.000Z' },
      { email: 'b@example.com', reason: 'bounce', suppressedAt: '2026-03-02T00:00:00.000Z' },
    ]

    // Hand-written DB-shaped store: only the interface methods, NO `suppressed` array
    // property. The old duck-typed `'suppressed' in store` path would return [] here.
    const dbStore: ComplianceStore = {
      async isSuppressed(email) {
        return known.some((e) => e.email === email)
      },
      async getConsent() {
        return null
      },
      async setConsent() {},
      async addSuppression() {},
      async listSuppressed() {
        return known
      },
    }

    expect('suppressed' in dbStore).toBe(false)

    const adapter = makeSelfManagedAdapter({
      from: 'news@example.com',
      mail: { send: vi.fn() },
      store: createFakeStore({ recipients: [] }),
      complianceStore: dbStore,
    })

    const entries = await adapter.syncSuppression()
    expect(entries).toEqual(known)
    expect(entries).toHaveLength(2)
  })

  it('exposes supports flags for host UI branching', () => {
    const adapter = makeSelfManagedAdapter({
      from: 'news@example.com',
      mail: { send: vi.fn() },
      store: createFakeStore(),
      complianceStore: createMemoryComplianceStore(),
    })

    expect(adapter.supports).toEqual({
      segments: true,
      scheduling: true,
      automation: false,
    })
  })
})
