import { it, expect, vi } from 'vitest'
import {
  createMail,
  MailProviderError,
  MailValidationError,
  render,
  validateMessage,
  type MailAdapter,
  type MailMessage,
  type MailResult,
} from './index.js'

function validMessage(overrides: Partial<MailMessage> = {}): MailMessage {
  return {
    from: 'sender@example.com',
    to: 'recipient@example.com',
    subject: 'Hello',
    text: 'Hi there',
    ...overrides,
  }
}

it('validateMessage rejects malformed addresses and missing html|text; accepts valid input', () => {
  expect(() =>
    validateMessage(validMessage({ from: 'not-an-email' })),
  ).toThrow(MailValidationError)
  expect(() =>
    validateMessage(validMessage({ to: 'also-bad' })),
  ).toThrow(MailValidationError)
  expect(() =>
    validateMessage(validMessage({ html: undefined, text: undefined })),
  ).toThrow(MailValidationError)
  expect(() => validateMessage(validMessage())).not.toThrow()
  expect(() =>
    validateMessage(
      validMessage({
        from: 'Sender <sender@example.com>',
        to: ['a@example.com', 'b@example.com'],
        html: '<p>Hi</p>',
        text: undefined,
      }),
    ),
  ).not.toThrow()
})

it('createMail validates then delegates; idempotencyKey passes through unchanged', async () => {
  let captured: MailMessage | undefined
  const fake: MailAdapter = {
    async send(msg) {
      captured = msg
      return { id: 'msg-1', provider: 'fake' }
    },
  }

  const mail = createMail(fake)
  const msg = validMessage({ idempotencyKey: 'idem-abc', html: '<p>x</p>' })
  const result = await mail.send(msg)

  expect(result).toEqual({ id: 'msg-1', provider: 'fake' })
  expect(captured).toEqual(msg)
  expect(captured?.idempotencyKey).toBe('idem-abc')
})

it('injected retry hook runs on transient adapter failure; MailValidationError is terminal', async () => {
  let attempts = 0
  const flaky: MailAdapter = {
    async send() {
      attempts += 1
      if (attempts === 1) {
        throw new MailProviderError('rate limited', 'fake', true)
      }
      return { id: 'msg-2', provider: 'fake' }
    },
  }

  const retry = vi.fn(async (attempt: () => Promise<MailResult>) => {
    try {
      return await attempt()
    } catch (err) {
      if (err instanceof MailProviderError && err.retryable) {
        return attempt()
      }
      throw err
    }
  })

  const mail = createMail(flaky, { retry })
  const result = await mail.send(validMessage())
  expect(result.id).toBe('msg-2')
  expect(retry).toHaveBeenCalledOnce()
  expect(attempts).toBe(2)

  const strict = createMail(flaky, { retry })
  await expect(
    strict.send(validMessage({ from: 'bad-address' })),
  ).rejects.toBeInstanceOf(MailValidationError)
  expect(retry).toHaveBeenCalledOnce()
})

it('render interpolates {{key}} and leaves missing keys blank', () => {
  expect(render('Hi {{name}}', { name: 'A' })).toBe('Hi A')
  expect(render('Hi {{name}}', {})).toBe('Hi ')
})
