import { describe, it, expect, vi, beforeEach } from 'vitest'

import Payment from '../../../server/models/payment.js'
import { upsertPaymentFromHelcim } from '../../../server/utils/payments.js'

vi.mock('../../../server/models/payment.js', () => ({
  default: {
    findOne: vi.fn(),
    create: vi.fn()
  }
}))

const sendMock = vi.fn().mockResolvedValue({ data: {}, error: null })
vi.mock('resend', () => ({
  Resend: function MockResend() {
    this.emails = { send: (...args) => sendMock(...args) }
  }
}))

const memberDoc = {
  _id: 'member-1',
  email: 'test@example.com',
  name: 'Test Member',
  helcimCustomerId: 'CST123',
  helcimSubscriptionId: 'sub-1',
  billingCadence: 'monthly'
}

const paidTx = {
  id: 'tx-100',
  date: '2026-04-20T10:00:00Z',
  amount: 15,
  status: 'paid',
  currency: 'CAD'
}

describe('upsertPaymentFromHelcim', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('inserts a new Payment when helcimTransactionId is unseen', async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p1', helcimTransactionId: 'tx-100' })

    const result = await upsertPaymentFromHelcim(memberDoc, paidTx)

    expect(result.created).toBe(true)
    expect(result.payment._id).toBe('p1')
    expect(Payment.create).toHaveBeenCalledWith(expect.objectContaining({
      memberId: 'member-1',
      helcimTransactionId: 'tx-100',
      helcimCustomerId: 'CST123',
      helcimSubscriptionId: 'sub-1',
      amount: 15,
      currency: 'CAD',
      paymentType: 'monthly',
      status: 'success',
      rawHelcim: paidTx
    }))
  })

  it('does NOT overwrite an existing Payment', async () => {
    const existing = { _id: 'p1', helcimTransactionId: 'tx-100', receiptIssued: true, receiptId: 'R-2026-001' }
    Payment.findOne.mockResolvedValue(existing)

    const result = await upsertPaymentFromHelcim(memberDoc, paidTx)

    expect(result.created).toBe(false)
    expect(result.payment).toBe(existing)
    expect(Payment.create).not.toHaveBeenCalled()
  })

  it("maps helcim status 'refunded' to 'refunded'", async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p2' })

    await upsertPaymentFromHelcim(memberDoc, { ...paidTx, id: 'tx-101', status: 'refunded' })

    expect(Payment.create).toHaveBeenCalledWith(expect.objectContaining({ status: 'refunded' }))
  })

  it("maps helcim status 'failed' to 'failed'", async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p3' })

    await upsertPaymentFromHelcim(memberDoc, { ...paidTx, id: 'tx-102', status: 'failed' })

    expect(Payment.create).toHaveBeenCalledWith(expect.objectContaining({ status: 'failed' }))
  })

  it("skips helcim status 'other' and returns created:false, payment:null", async () => {
    const result = await upsertPaymentFromHelcim(memberDoc, { ...paidTx, id: 'tx-103', status: 'other' })

    expect(result).toEqual({ created: false, payment: null })
    expect(Payment.findOne).not.toHaveBeenCalled()
    expect(Payment.create).not.toHaveBeenCalled()
  })

  it('uses opts.paymentType when provided, overriding memberDoc.billingCadence', async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p4' })

    await upsertPaymentFromHelcim(memberDoc, paidTx, { paymentType: 'annual' })

    expect(Payment.create).toHaveBeenCalledWith(expect.objectContaining({ paymentType: 'annual' }))
  })

  it('falls back to memberDoc.billingCadence when opts.paymentType is absent', async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p5' })

    await upsertPaymentFromHelcim({ ...memberDoc, billingCadence: 'annual' }, paidTx)

    expect(Payment.create).toHaveBeenCalledWith(expect.objectContaining({ paymentType: 'annual' }))
  })

  it('sends Resend confirmation when sendConfirmation:true AND created:true', async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p6' })

    await upsertPaymentFromHelcim(memberDoc, paidTx, { sendConfirmation: true })

    expect(sendMock).toHaveBeenCalledTimes(1)
    const emailArg = sendMock.mock.calls[0][0]
    expect(emailArg.to).toContain('test@example.com')
    expect(emailArg.text).toContain('Baby Ghosts Studio Development Fund')
    expect(emailArg.text).toContain('not an official donation receipt')
    expect(emailArg.text).toContain('available starting later in 2026')
  })

  it('does NOT send confirmation when sendConfirmation:true but created:false', async () => {
    Payment.findOne.mockResolvedValue({ _id: 'existing' })

    await upsertPaymentFromHelcim(memberDoc, paidTx, { sendConfirmation: true })

    expect(sendMock).not.toHaveBeenCalled()
  })

  it('does NOT send confirmation when sendConfirmation is absent/false', async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p7' })

    await upsertPaymentFromHelcim(memberDoc, paidTx)

    expect(sendMock).not.toHaveBeenCalled()
  })

  it('swallows Resend send errors (logging must not break payment flow)', async () => {
    Payment.findOne.mockResolvedValue(null)
    Payment.create.mockResolvedValue({ _id: 'p8' })
    sendMock.mockRejectedValueOnce(new Error('SMTP blew up'))

    const result = await upsertPaymentFromHelcim(memberDoc, paidTx, { sendConfirmation: true })

    expect(result.created).toBe(true)
    expect(result.payment._id).toBe('p8')
  })
})
