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

import Member from '../../../server/models/member.js'
import {
  requiresPayment,
  getHelcimPlanId,
} from '../../../server/config/contributions.js'
import {
  updateHelcimSubscription,
  getHelcimCustomer,
  listHelcimCustomerCards,
  createHelcimSubscription,
  cancelHelcimSubscription,
  listHelcimCustomerTransactions,
} from '../../../server/utils/helcim.js'
import { upsertPaymentFromHelcim } from '../../../server/utils/payments.js'
import handler from '../../../server/api/members/update-contribution.post.js'
import { createMockEvent } from '../helpers/createMockEvent.js'

vi.mock('../../../server/models/member.js', () => ({
  default: { findByIdAndUpdate: vi.fn() }
}))
vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() }))
vi.mock('../../../server/utils/auth.js', () => ({ requireAuth: vi.fn() }))
vi.mock('../../../server/config/contributions.js', () => ({
  requiresPayment: vi.fn(),
  getHelcimPlanId: vi.fn(),
}))
vi.mock('../../../server/utils/helcim.js', () => ({
  getHelcimCustomer: vi.fn(),
  listHelcimCustomerCards: vi.fn(),
  createHelcimSubscription: vi.fn(),
  updateHelcimSubscription: vi.fn(),
  cancelHelcimSubscription: vi.fn(),
  generateIdempotencyKey: vi.fn().mockReturnValue('idem-key-123'),
  listHelcimCustomerTransactions: vi.fn().mockResolvedValue([]),
}))
vi.mock('../../../server/utils/payments.js', () => ({
  upsertPaymentFromHelcim: vi.fn().mockResolvedValue({ created: true, payment: { _id: 'p1' } })
}))

// Nitro auto-imports
vi.stubGlobal('updateContributionSchema', {})

describe('update-contribution endpoint — Case 3 (paid→paid)', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    // Both tiers require payment for all Case 3 tests
    requiresPayment.mockReturnValue(true)
  })

  // Helper: set requireAuth global to resolve with the given member
  function setMember(mockMember) {
    globalThis.requireAuth = vi.fn().mockResolvedValue(mockMember)
  }

  it('monthly $5 → $15: calls updateHelcimSubscription with recurringAmount and updates member', async () => {
    const mockMember = {
      _id: 'member-1',
      contributionAmount: 5,
      helcimSubscriptionId: 'sub-1',
      billingCadence: 'monthly',
    }
    setMember(mockMember)
    updateHelcimSubscription.mockResolvedValue({ id: 'sub-1', status: 'active' })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15 },
    })

    const result = await handler(event)

    expect(updateHelcimSubscription).toHaveBeenCalledWith('sub-1', { recurringAmount: 15 })
    expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
      'member-1',
      { $set: { contributionAmount: 15 } },
      { runValidators: false }
    )
    expect(result.success).toBe(true)
    expect(result.message).toBe('Successfully updated contribution level')
  })

  it('annual $5 → $15: calls updateHelcimSubscription with recurringAmount 180', async () => {
    const mockMember = {
      _id: 'member-2',
      contributionAmount: 5,
      helcimSubscriptionId: 'sub-2',
      billingCadence: 'annual',
    }
    setMember(mockMember)
    updateHelcimSubscription.mockResolvedValue({ id: 'sub-2', status: 'active' })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15, cadence: 'annual' },
    })

    const result = await handler(event)

    expect(updateHelcimSubscription).toHaveBeenCalledWith('sub-2', { recurringAmount: 180 })
    expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
      'member-2',
      { $set: { contributionAmount: 15 } },
      { runValidators: false }
    )
    expect(result.success).toBe(true)
  })

  it('annual $15 → $50: calls updateHelcimSubscription with recurringAmount 600', async () => {
    const mockMember = {
      _id: 'member-3',
      contributionAmount: 15,
      helcimSubscriptionId: 'sub-3',
      billingCadence: 'annual',
    }
    setMember(mockMember)
    updateHelcimSubscription.mockResolvedValue({ id: 'sub-3', status: 'active' })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 50, cadence: 'annual' },
    })

    await handler(event)

    expect(updateHelcimSubscription).toHaveBeenCalledWith('sub-3', { recurringAmount: 600 })
  })

  it('cadence mismatch: monthly member + body cadence annual → 400, no Helcim call, no DB write', async () => {
    const mockMember = {
      _id: 'member-4',
      contributionAmount: 5,
      helcimSubscriptionId: 'sub-4',
      billingCadence: 'monthly',
    }
    setMember(mockMember)

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15, cadence: 'annual' },
    })

    await expect(handler(event)).rejects.toMatchObject({
      statusCode: 400,
      statusMessage: 'Cadence switch not supported on existing subscription',
    })

    expect(updateHelcimSubscription).not.toHaveBeenCalled()
    expect(Member.findByIdAndUpdate).not.toHaveBeenCalled()
  })

  it('Helcim PATCH failure → 500, member NOT updated', async () => {
    const mockMember = {
      _id: 'member-5',
      contributionAmount: 5,
      helcimSubscriptionId: 'sub-5',
      billingCadence: 'monthly',
    }
    setMember(mockMember)
    updateHelcimSubscription.mockRejectedValue(new Error('Helcim 400'))

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15 },
    })

    await expect(handler(event)).rejects.toMatchObject({
      statusCode: 500,
      statusMessage: 'Subscription update failed',
    })

    expect(Member.findByIdAndUpdate).not.toHaveBeenCalled()
  })

  it('no helcimSubscriptionId → 400 with requiresPaymentSetup, no Helcim call', async () => {
    const mockMember = {
      _id: 'member-6',
      contributionAmount: 5,
      helcimSubscriptionId: null,
      billingCadence: 'monthly',
    }
    setMember(mockMember)

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15 },
    })

    await expect(handler(event)).rejects.toMatchObject({
      statusCode: 400,
      data: { requiresPaymentSetup: true },
    })

    expect(updateHelcimSubscription).not.toHaveBeenCalled()
    expect(Member.findByIdAndUpdate).not.toHaveBeenCalled()
  })
})

describe('update-contribution endpoint — Case 1 (free→paid)', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    // old amount = 0 (free), new amount > 0 (paid)
    requiresPayment.mockImplementation((amount) => amount !== 0)
  })

  function setMember(mockMember) {
    globalThis.requireAuth = vi.fn().mockResolvedValue(mockMember)
  }

  const freeMember = {
    _id: 'member-c1',
    contributionAmount: 0,
    helcimCustomerId: 'cust-1',
  }

  it('monthly: calls createHelcimSubscription with monthly plan id and recurringAmount 15, persists billingCadence monthly', async () => {
    setMember(freeMember)
    getHelcimPlanId.mockReturnValue('111')
    getHelcimCustomer.mockResolvedValue({ customerCode: 'code-1' })
    listHelcimCustomerCards.mockResolvedValue([{ id: 'card-1' }])
    createHelcimSubscription.mockResolvedValue({ data: [{ id: 'sub-new', status: 'active', nextBillingDate: '2026-05-18' }] })
    Member.findByIdAndUpdate.mockResolvedValue({})
    listHelcimCustomerTransactions.mockResolvedValueOnce([
      { id: 'tx-upgrade', date: '2026-04-20', amount: 15, status: 'paid', currency: 'CAD' }
    ])

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15, cadence: 'monthly' },
    })

    const result = await handler(event)

    expect(createHelcimSubscription).toHaveBeenCalledWith(
      expect.objectContaining({ paymentPlanId: 111, recurringAmount: 15 }),
      'idem-key-123'
    )
    expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
      'member-c1',
      { $set: expect.objectContaining({ billingCadence: 'monthly', contributionAmount: 15, helcimSubscriptionId: 'sub-new' }) },
      { runValidators: false }
    )
    expect(listHelcimCustomerTransactions).toHaveBeenCalledWith('code-1')
    expect(upsertPaymentFromHelcim).toHaveBeenCalledWith(
      expect.objectContaining({ _id: 'member-c1', helcimSubscriptionId: 'sub-new', billingCadence: 'monthly' }),
      expect.objectContaining({ id: 'tx-upgrade' }),
      { paymentType: 'monthly', sendConfirmation: true }
    )
    expect(result.success).toBe(true)
    expect(result.message).toBe('Successfully upgraded to paid tier')
  })

  it('annual: calls createHelcimSubscription with annual plan id and recurringAmount 180, persists billingCadence annual', async () => {
    setMember(freeMember)
    getHelcimPlanId.mockReturnValue('222')
    getHelcimCustomer.mockResolvedValue({ customerCode: 'code-1' })
    listHelcimCustomerCards.mockResolvedValue([{ id: 'card-1' }])
    createHelcimSubscription.mockResolvedValue({ data: [{ id: 'sub-annual', status: 'active', nextBillingDate: '2027-04-18' }] })
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15, cadence: 'annual' },
    })

    const result = await handler(event)

    expect(createHelcimSubscription).toHaveBeenCalledWith(
      expect.objectContaining({ paymentPlanId: 222, recurringAmount: 180 }),
      'idem-key-123'
    )
    expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
      'member-c1',
      { $set: expect.objectContaining({ billingCadence: 'annual', contributionAmount: 15, helcimSubscriptionId: 'sub-annual' }) },
      { runValidators: false }
    )
    expect(result.success).toBe(true)
  })

  it('missing plan id env → 500, createHelcimSubscription NOT called, member NOT updated', async () => {
    setMember(freeMember)
    getHelcimPlanId.mockReturnValue(null)

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 15, cadence: 'monthly' },
    })

    await expect(handler(event)).rejects.toMatchObject({
      statusCode: 500,
      statusMessage: 'Monthly plan id not configured',
    })

    expect(createHelcimSubscription).not.toHaveBeenCalled()
    expect(Member.findByIdAndUpdate).not.toHaveBeenCalled()
  })
})

describe('update-contribution endpoint — Case 2 (paid→free)', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    // old amount > 0 (paid), new amount = 0 (free)
    requiresPayment.mockImplementation((amount) => amount !== 0)
  })

  function setMember(mockMember) {
    globalThis.requireAuth = vi.fn().mockResolvedValue(mockMember)
  }

  it('cancels subscription, resets billingCadence to monthly, clears helcimSubscriptionId', async () => {
    const mockMember = {
      _id: 'member-c2',
      contributionAmount: 15,
      helcimSubscriptionId: 'sub-1',
      billingCadence: 'monthly',
    }
    setMember(mockMember)
    cancelHelcimSubscription.mockResolvedValue({})
    Member.findByIdAndUpdate.mockResolvedValue({})

    const event = createMockEvent({
      method: 'POST',
      path: '/api/members/update-contribution',
      body: { contributionAmount: 0 },
    })

    const result = await handler(event)

    expect(cancelHelcimSubscription).toHaveBeenCalledWith('sub-1')
    expect(Member.findByIdAndUpdate).toHaveBeenCalledWith(
      'member-c2',
      expect.objectContaining({
        $set: expect.objectContaining({
          contributionAmount: 0,
          helcimSubscriptionId: null,
          paymentMethod: 'none',
          billingCadence: 'monthly',
        }),
        $unset: expect.objectContaining({ nextBillingDate: 1 }),
      }),
      { runValidators: false }
    )
    expect(result.success).toBe(true)
    expect(result.message).toBe('Successfully downgraded to free tier')
  })
})
