import { describe, expect, it } from 'vitest'
import {
  DEFAULT_PREFERENCES, DEFAULT_SOFT_BOUNCE_POLICY, EMAIL_CATEGORIES, TEMPLATE_CATEGORY, campaignPauseDecision, normalizeEventType,
  preSendDecision, resolveTemplateMeta, softBounceDecision, unsubscribeFromUpdates, validateTemplateMeta,
} from '@/lib/domain/email-policy'
import { DEFAULT_TEMPLATES } from '@/lib/email-templates'

/** Two streams, one policy (2026-09-22): marketing unsubscribe is category-specific; hard bounce is delivery-wide. */
const meta = (key: string) => resolveTemplateMeta(key)

describe('template → stream', () => {
  it('every shipped template has a category, and categories agree with their stream', () => {
    for (const t of DEFAULT_TEMPLATES) expect(TEMPLATE_CATEGORY[t.key], t.key).toBeDefined()
    for (const c of EMAIL_CATEGORIES) expect(validateTemplateMeta({ email_stream: c.stream, category_key: c.key, required: c.required }).ok).toBe(true)
  })
  it('rejects marketing through transactional and required marketing', () => {
    expect(validateTemplateMeta({ email_stream: 'transactional', category_key: 'newsletter', required: false }).ok).toBe(false)
    expect(validateTemplateMeta({ email_stream: 'updates', category_key: 'account_security', required: true }).ok).toBe(false)
    expect(validateTemplateMeta({ email_stream: 'updates', category_key: 'product_updates', required: true }).ok).toBe(false)
  })
  it('invitations from a Company Admin are transactional; cold invitations are updates', () => {
    expect(meta('broker_company_invitation').stream).toBe('transactional')
    expect(meta('broker_lead_invitation').stream).toBe('updates')
    expect(meta('pilot_invitation_initial').stream).toBe('updates')
  })
})

describe('pre-send decision (§31–§35)', () => {
  const prefsNoMarketing = { ...unsubscribeFromUpdates(DEFAULT_PREFERENCES) }
  it('Nash example: marketing off → password reset SEND, permit SEND, verification SEND, newsletter and pilot invitation DO NOT SEND', () => {
    const ok = { templateActive: true, suppressions: [], delivery: null, preferences: prefsNoMarketing }
    expect(preSendDecision({ ...ok, meta: { stream: 'transactional', category: 'account_security', required: true } }).allowed).toBe(true)
    expect(preSendDecision({ ...ok, meta: meta('carrier_upload_request') }).allowed).toBe(true)
    expect(preSendDecision({ ...ok, meta: meta('broker_relationship_verification_request') }).allowed).toBe(true)
    expect(preSendDecision({ ...ok, meta: { stream: 'updates', category: 'newsletter', required: false } })).toEqual({ allowed: false, reason: 'preference_off' })
    expect(preSendDecision({ ...ok, meta: meta('pilot_invitation_initial') })).toEqual({ allowed: false, reason: 'preference_off' })
  })
  it('hard bounce stops everything, transactional included (§33)', () => {
    const dead = { templateActive: true, suppressions: [{ scope: 'global' as const, campaign_type: null, expires_at: null, overridden_at: null }], delivery: null, preferences: DEFAULT_PREFERENCES }
    expect(preSendDecision({ ...dead, meta: { stream: 'transactional', category: 'account_security', required: true } })).toEqual({ allowed: false, reason: 'global_suppression' })
    expect(preSendDecision({ templateActive: true, suppressions: [], delivery: { status: 'hard_bounced' }, preferences: null, meta: meta('company_relationship_approved') })).toEqual({ allowed: false, reason: 'hard_bounced' })
  })
  it('marketing suppression (spam complaint) blocks updates only; an overridden or expired suppression does not count', () => {
    const past = new Date(Date.now() - 1000).toISOString()
    const sup = [{ scope: 'marketing' as const, campaign_type: null, expires_at: null, overridden_at: null }]
    expect(preSendDecision({ templateActive: true, suppressions: sup, delivery: null, preferences: DEFAULT_PREFERENCES, meta: { stream: 'updates', category: 'product_updates', required: false } }).allowed).toBe(false)
    expect(preSendDecision({ templateActive: true, suppressions: sup, delivery: null, preferences: DEFAULT_PREFERENCES, meta: { stream: 'transactional', category: 'account_security', required: true } }).allowed).toBe(true)
    expect(preSendDecision({ templateActive: true, suppressions: [{ ...sup[0], expires_at: past }], delivery: null, preferences: DEFAULT_PREFERENCES, meta: { stream: 'updates', category: 'product_updates', required: false } }).allowed).toBe(true)
    expect(preSendDecision({ templateActive: true, suppressions: [{ ...sup[0], overridden_at: past }], delivery: null, preferences: DEFAULT_PREFERENCES, meta: { stream: 'updates', category: 'product_updates', required: false } }).allowed).toBe(true)
  })
  it('optional operational categories honour the preference; required ones ignore it', () => {
    const prefs = { ...DEFAULT_PREFERENCES, permit_notifications: false }
    expect(preSendDecision({ templateActive: true, suppressions: [], delivery: null, preferences: prefs, meta: meta('carrier_upload_request') }).allowed).toBe(false)
    expect(preSendDecision({ templateActive: true, suppressions: [], delivery: null, preferences: prefs, meta: meta('company_relationship_approved') }).allowed).toBe(true)
    expect(meta('permit_dimension_alert')).toEqual({ stream: 'transactional', category: 'permit_notifications', required: true })
    expect(preSendDecision({ templateActive: true, suppressions: [], delivery: null, preferences: prefs, meta: meta('permit_dimension_alert') }).allowed).toBe(true)
    expect(preSendDecision({ templateActive: false, suppressions: [], delivery: null, preferences: prefs, meta: meta('company_relationship_approved') })).toEqual({ allowed: false, reason: 'template_inactive' })
  })
})

describe('webhook normalisation + bounce rules', () => {
  it('maps ZeptoMail event names', () => {
    expect(normalizeEventType('hardbounce')).toBe('hard_bounce')
    expect(normalizeEventType('softbounce')).toBe('soft_bounce')
    expect(normalizeEventType('email_delivered')).toBe('delivered')
    expect(normalizeEventType('email_link_click')).toBe('clicked')
    expect(normalizeEventType('email_open')).toBe('opened')
    expect(normalizeEventType('feedback_loop')).toBe('spam_complaint')
  })
  it('soft bounces: record, record, suppress on the third inside the window; a stale window restarts', () => {
    const now = Date.parse('2026-09-22T00:00:00Z')
    const first = softBounceDecision({ soft_bounce_count: 0, first_soft_bounce_at: null }, DEFAULT_SOFT_BOUNCE_POLICY, now)
    expect(first).toMatchObject({ count: 1, suppress: false })
    const third = softBounceDecision({ soft_bounce_count: 2, first_soft_bounce_at: new Date(now - 3 * 86_400_000).toISOString() }, DEFAULT_SOFT_BOUNCE_POLICY, now)
    expect(third).toMatchObject({ count: 3, suppress: true })
    const stale = softBounceDecision({ soft_bounce_count: 2, first_soft_bounce_at: new Date(now - 30 * 86_400_000).toISOString() }, DEFAULT_SOFT_BOUNCE_POLICY, now)
    expect(stale).toMatchObject({ count: 1, suppress: false })
  })
  it('campaign pause: 5% hard bounces after 20 sends', () => {
    expect(campaignPauseDecision({ sent_count: 10, hard_bounce_count: 5 }, { minSent: 20, pausePct: 5 })).toBe(false)
    expect(campaignPauseDecision({ sent_count: 40, hard_bounce_count: 1 }, { minSent: 20, pausePct: 5 })).toBe(false)
    expect(campaignPauseDecision({ sent_count: 40, hard_bounce_count: 2 }, { minSent: 20, pausePct: 5 })).toBe(true)
  })
})
