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

import { requireAuth } from '../../../server/utils/auth.js'
import { validateBody } from '../../../server/utils/validateBody.js'
import {
  listHelcimCustomerCards,
  updateHelcimCustomerDefaultPaymentMethod,
  updateHelcimSubscriptionPaymentMethod
} from '../../../server/utils/helcim.js'
import { logActivity } from '../../../server/utils/activityLog.js'
import updateCardHandler from '../../../server/api/helcim/update-card.post.js'
import { createMockEvent } from '../helpers/createMockEvent.js'

vi.mock('../../../server/utils/auth.js', () => ({ requireAuth: vi.fn() }))
vi.mock('../../../server/utils/validateBody.js', () => ({ validateBody: vi.fn() }))
vi.mock('../../../server/utils/schemas.js', () => ({ helcimUpdateCardSchema: {} }))
vi.mock('../../../server/utils/helcim.js', () => ({
  listHelcimCustomerCards: vi.fn(),
  updateHelcimCustomerDefaultPaymentMethod: vi.fn(),
  updateHelcimSubscriptionPaymentMethod: vi.fn()
}))
vi.mock('../../../server/utils/activityLog.js', () => ({ logActivity: vi.fn() }))

const activeMember = () => ({
  _id: 'member-1',
  status: 'active',
  helcimCustomerId: 9876,
  helcimSubscriptionId: 5555
})

const cardsList = () => ([
  { cardToken: 'tok-old', id: 1, default: true },
  { cardToken: 'tok-new', id: 2 }
])

const newEvent = () =>
  createMockEvent({
    method: 'POST',
    path: '/api/helcim/update-card',
    body: { cardToken: 'tok-new' }
  })

describe('helcim update-card endpoint', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    validateBody.mockResolvedValue({ cardToken: 'tok-new' })
  })

  it('happy path: updates customer default + subscription, logs activity, returns success', async () => {
    requireAuth.mockResolvedValue(activeMember())
    listHelcimCustomerCards.mockResolvedValue(cardsList())
    updateHelcimCustomerDefaultPaymentMethod.mockResolvedValue({})
    updateHelcimSubscriptionPaymentMethod.mockResolvedValue({})

    const result = await updateCardHandler(newEvent())

    expect(result).toEqual({ success: true })
    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenCalledTimes(1)
    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenCalledWith(9876, 'tok-new')
    expect(updateHelcimSubscriptionPaymentMethod).toHaveBeenCalledWith(5555, 'tok-new')
    expect(logActivity).toHaveBeenCalledWith(
      'member-1',
      'billing_card_updated',
      {},
      { visibility: 'member' }
    )
  })

  it('returns 401 when unauthenticated', async () => {
    requireAuth.mockRejectedValue(
      createError({ statusCode: 401, statusMessage: 'Unauthorized' })
    )

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 401 })

    expect(listHelcimCustomerCards).not.toHaveBeenCalled()
    expect(updateHelcimCustomerDefaultPaymentMethod).not.toHaveBeenCalled()
    expect(updateHelcimSubscriptionPaymentMethod).not.toHaveBeenCalled()
  })

  it('returns 403 when member status is not active/pending_payment', async () => {
    requireAuth.mockResolvedValue({ ...activeMember(), status: 'cancelled' })

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 403 })

    expect(listHelcimCustomerCards).not.toHaveBeenCalled()
    expect(updateHelcimCustomerDefaultPaymentMethod).not.toHaveBeenCalled()
    expect(updateHelcimSubscriptionPaymentMethod).not.toHaveBeenCalled()
  })

  it('returns 400 when member is missing helcimCustomerId', async () => {
    requireAuth.mockResolvedValue({ ...activeMember(), helcimCustomerId: null })

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 400 })

    expect(listHelcimCustomerCards).not.toHaveBeenCalled()
    expect(updateHelcimCustomerDefaultPaymentMethod).not.toHaveBeenCalled()
  })

  it('returns 400 when member is missing helcimSubscriptionId', async () => {
    requireAuth.mockResolvedValue({ ...activeMember(), helcimSubscriptionId: null })

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 400 })

    expect(listHelcimCustomerCards).not.toHaveBeenCalled()
  })

  it('returns 403 when cardToken is not attached to the customer', async () => {
    requireAuth.mockResolvedValue(activeMember())
    listHelcimCustomerCards.mockResolvedValue([
      { cardToken: 'tok-old', id: 1, default: true }
    ])

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 403 })

    expect(updateHelcimCustomerDefaultPaymentMethod).not.toHaveBeenCalled()
    expect(updateHelcimSubscriptionPaymentMethod).not.toHaveBeenCalled()
  })

  it('returns 500 and does NOT call subscription update when customer default update throws', async () => {
    requireAuth.mockResolvedValue(activeMember())
    listHelcimCustomerCards.mockResolvedValue(cardsList())
    updateHelcimCustomerDefaultPaymentMethod.mockRejectedValue(
      createError({ statusCode: 500, statusMessage: 'Helcim down' })
    )

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 500 })

    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenCalledTimes(1)
    expect(updateHelcimSubscriptionPaymentMethod).not.toHaveBeenCalled()
    expect(logActivity).not.toHaveBeenCalled()
  })

  it('partial failure with 5xx: reverts customer default with prior token and returns 500', async () => {
    requireAuth.mockResolvedValue(activeMember())
    listHelcimCustomerCards.mockResolvedValue(cardsList())
    updateHelcimCustomerDefaultPaymentMethod.mockResolvedValue({})
    updateHelcimSubscriptionPaymentMethod.mockRejectedValue(
      createError({ statusCode: 502, statusMessage: 'Bad gateway' })
    )

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 500 })

    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenCalledTimes(2)
    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenNthCalledWith(1, 9876, 'tok-new')
    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenNthCalledWith(2, 9876, 'tok-old')
    expect(logActivity).not.toHaveBeenCalled()
  })

  it('subscription PATCH 4xx is tolerated: returns success without revert', async () => {
    requireAuth.mockResolvedValue(activeMember())
    listHelcimCustomerCards.mockResolvedValue(cardsList())
    updateHelcimCustomerDefaultPaymentMethod.mockResolvedValue({})
    updateHelcimSubscriptionPaymentMethod.mockRejectedValue(
      createError({ statusCode: 400, statusMessage: 'Unknown field: cardToken' })
    )

    const result = await updateCardHandler(newEvent())

    expect(result).toEqual({ success: true })
    // customer default was set once, no revert
    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenCalledTimes(1)
    expect(logActivity).toHaveBeenCalledWith(
      'member-1',
      'billing_card_updated',
      {},
      { visibility: 'member' }
    )
  })

  it('revert-also-fails: returns 500 and inconsistency is logged', async () => {
    requireAuth.mockResolvedValue(activeMember())
    listHelcimCustomerCards.mockResolvedValue(cardsList())
    // First call (set new default) succeeds; second call (revert) fails
    updateHelcimCustomerDefaultPaymentMethod
      .mockResolvedValueOnce({})
      .mockRejectedValueOnce(createError({ statusCode: 500, statusMessage: 'still down' }))
    updateHelcimSubscriptionPaymentMethod.mockRejectedValue(
      createError({ statusCode: 503, statusMessage: 'Service unavailable' })
    )

    const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})

    await expect(updateCardHandler(newEvent())).rejects.toMatchObject({ statusCode: 500 })

    expect(updateHelcimCustomerDefaultPaymentMethod).toHaveBeenCalledTimes(2)

    const loggedInconsistency = errorSpy.mock.calls.some(([msg]) =>
      typeof msg === 'string' && msg.includes('INCONSISTENT STATE')
    )
    expect(loggedInconsistency).toBe(true)
    expect(logActivity).not.toHaveBeenCalled()

    errorSpy.mockRestore()
  })
})
