const { describe, test, expect, beforeEach, afterEach } = require('bun:test')
const crypto = require('crypto')

const MIN_PAYMENT_AMOUNT = 50

const SUBSCRIPTION_PRICES = {
  starter_monthly_usd: 750,
  starter_yearly_usd: 7500,
  starter_monthly_gbp: 625,
  starter_yearly_gbp: 6250,
  starter_monthly_eur: 750,
  starter_yearly_eur: 7500,
  
  professional_monthly_usd: 2500,
  professional_yearly_usd: 25000,
  professional_monthly_gbp: 2100,
  professional_yearly_gbp: 21000,
  professional_monthly_eur: 2500,
  professional_yearly_eur: 25000,
  
  enterprise_monthly_usd: 7500,
  enterprise_yearly_usd: 75000,
  enterprise_monthly_gbp: 6250,
  enterprise_yearly_gbp: 62500,
  enterprise_monthly_eur: 7500,
  enterprise_yearly_eur: 75000
}

const TOPUP_PRICES = {
  topup_1_usd: 750,
  topup_1_gbp: 500,
  topup_1_eur: 750,
  topup_2_usd: 2500,
  topup_2_gbp: 2000,
  topup_2_eur: 2500,
  topup_3_usd: 7500,
  topup_3_gbp: 6000,
  topup_3_eur: 7500,
  topup_4_usd: 12500,
  topup_4_gbp: 10000,
  topup_4_eur: 12500
}

const TOPUP_TOKENS = {
  topup_1: 100_000,
  topup_2: 5_000_000,
  topup_3: 20_000_000,
  topup_4: 50_000_000
}

const PAYG_PRICES = {
  usd: 250,
  gbp: 200,
  eur: 250
}

const BUSINESS_TOPUP_DISCOUNT = 0.025
const ENTERPRISE_TOPUP_DISCOUNT = 0.05

const BILLING_CYCLES = ['monthly', 'yearly']
const SUPPORTED_CURRENCIES = ['usd', 'gbp', 'eur']

function formatPrice(cents, currency) {
  const symbols = { usd: '$', gbp: '£', eur: '€' }
  return `${symbols[currency] || '$'}${(cents / 100).toFixed(2)}`
}

function getProductKey(plan, cycle, currency) {
  return `${plan}_${cycle}_${currency}`
}

function getTopupProductKey(option, currency) {
  return `topup_${option}_${currency}`
}

function calculateYearlySavings(monthlyPrice, yearlyPrice) {
  return (monthlyPrice * 12) - yearlyPrice
}

function applyDiscount(price, discountRate) {
  return Math.round(price * (1 - discountRate))
}

function generatePaymentId() {
  return `pay_${crypto.randomBytes(16).toString('hex')}`
}

function validatePaymentAmount(amount) {
  return Number.isFinite(amount) && amount >= MIN_PAYMENT_AMOUNT
}

function parseWebhookPayload(payload) {
  try {
    return JSON.parse(payload)
  } catch {
    return null
  }
}

describe('Payments', () => {
  describe('Subscription Pricing', () => {
    test('has all plan prices defined', () => {
      const plans = ['starter', 'professional', 'enterprise']
      const cycles = ['monthly', 'yearly']
      const currencies = ['usd', 'gbp', 'eur']
      
      plans.forEach(plan => {
        cycles.forEach(cycle => {
          currencies.forEach(currency => {
            const key = getProductKey(plan, cycle, currency)
            expect(SUBSCRIPTION_PRICES[key]).toBeDefined()
            expect(SUBSCRIPTION_PRICES[key]).toBeGreaterThan(0)
          })
        })
      })
    })

    test('yearly price offers discount', () => {
      const plans = ['starter', 'professional', 'enterprise']
      
      plans.forEach(plan => {
        const monthlyPrice = SUBSCRIPTION_PRICES[getProductKey(plan, 'monthly', 'usd')]
        const yearlyPrice = SUBSCRIPTION_PRICES[getProductKey(plan, 'yearly', 'usd')]
        const savings = calculateYearlySavings(monthlyPrice, yearlyPrice)
        
        expect(yearlyPrice).toBeLessThan(monthlyPrice * 12)
        expect(savings).toBe(monthlyPrice * 2)
      })
    })

    test('enterprise is most expensive', () => {
      expect(SUBSCRIPTION_PRICES.enterprise_monthly_usd).toBeGreaterThan(SUBSCRIPTION_PRICES.professional_monthly_usd)
      expect(SUBSCRIPTION_PRICES.professional_monthly_usd).toBeGreaterThan(SUBSCRIPTION_PRICES.starter_monthly_usd)
    })

    test('formats prices correctly', () => {
      expect(formatPrice(750, 'usd')).toBe('$7.50')
      expect(formatPrice(625, 'gbp')).toBe('£6.25')
      expect(formatPrice(750, 'eur')).toBe('€7.50')
    })

    test('generates product keys correctly', () => {
      expect(getProductKey('starter', 'monthly', 'usd')).toBe('starter_monthly_usd')
      expect(getProductKey('professional', 'yearly', 'gbp')).toBe('professional_yearly_gbp')
    })
  })

  describe('Top-up Pricing', () => {
    test('has all top-up options defined', () => {
      const options = ['1', '2', '3', '4']
      const currencies = ['usd', 'gbp', 'eur']
      
      options.forEach(option => {
        currencies.forEach(currency => {
          const key = getTopupProductKey(option, currency)
          expect(TOPUP_PRICES[key]).toBeDefined()
          expect(TOPUP_PRICES[key]).toBeGreaterThan(0)
        })
      })
    })

    test('token amounts are correct', () => {
      expect(TOPUP_TOKENS.topup_1).toBe(100_000)
      expect(TOPUP_TOKENS.topup_2).toBe(5_000_000)
      expect(TOPUP_TOKENS.topup_3).toBe(20_000_000)
      expect(TOPUP_TOKENS.topup_4).toBe(50_000_000)
    })

    test('larger top-ups have better value', () => {
      const pricePerToken1 = TOPUP_PRICES.topup_1_usd / TOPUP_TOKENS.topup_1
      const pricePerToken2 = TOPUP_PRICES.topup_2_usd / TOPUP_TOKENS.topup_2
      const pricePerToken3 = TOPUP_PRICES.topup_3_usd / TOPUP_TOKENS.topup_3
      const pricePerToken4 = TOPUP_PRICES.topup_4_usd / TOPUP_TOKENS.topup_4
      
      expect(pricePerToken2).toBeLessThan(pricePerToken1)
      expect(pricePerToken3).toBeLessThan(pricePerToken2)
      expect(pricePerToken4).toBeLessThan(pricePerToken3)
    })

    test('calculates tokens per dollar', () => {
      const tokensPerDollar1 = TOPUP_TOKENS.topup_1 / (TOPUP_PRICES.topup_1_usd / 100)
      const tokensPerDollar4 = TOPUP_TOKENS.topup_4 / (TOPUP_PRICES.topup_4_usd / 100)
      
      expect(tokensPerDollar1).toBeCloseTo(13333.33, 0)
      expect(tokensPerDollar4).toBeCloseTo(400000, 0)
      expect(tokensPerDollar4).toBeGreaterThan(tokensPerDollar1)
    })
  })

  describe('Discounts', () => {
    test('applies business discount', () => {
      const originalPrice = 1000
      const discountedPrice = applyDiscount(originalPrice, BUSINESS_TOPUP_DISCOUNT)
      
      expect(discountedPrice).toBe(975)
    })

    test('applies enterprise discount', () => {
      const originalPrice = 1000
      const discountedPrice = applyDiscount(originalPrice, ENTERPRISE_TOPUP_DISCOUNT)
      
      expect(discountedPrice).toBe(950)
    })

    test('enterprise discount is higher than business', () => {
      expect(ENTERPRISE_TOPUP_DISCOUNT).toBeGreaterThan(BUSINESS_TOPUP_DISCOUNT)
    })

    test('discount rates are valid percentages', () => {
      expect(BUSINESS_TOPUP_DISCOUNT).toBeGreaterThanOrEqual(0)
      expect(BUSINESS_TOPUP_DISCOUNT).toBeLessThan(1)
      expect(ENTERPRISE_TOPUP_DISCOUNT).toBeGreaterThanOrEqual(0)
      expect(ENTERPRISE_TOPUP_DISCOUNT).toBeLessThan(1)
    })
  })

  describe('Pay-as-you-go', () => {
    test('has prices for all currencies', () => {
      SUPPORTED_CURRENCIES.forEach(currency => {
        expect(PAYG_PRICES[currency]).toBeDefined()
        expect(PAYG_PRICES[currency]).toBeGreaterThan(0)
      })
    })

    test('calculates PAYG cost correctly', () => {
      const tokens = 500_000
      const cost = (tokens / 1_000_000) * PAYG_PRICES.usd
      
      expect(cost).toBe(125)
    })

    test('GBP is cheaper than USD/EUR for PAYG', () => {
      expect(PAYG_PRICES.gbp).toBeLessThan(PAYG_PRICES.usd)
      expect(PAYG_PRICES.gbp).toBeLessThan(PAYG_PRICES.eur)
    })
  })

  describe('Payment Validation', () => {
    test('validates minimum payment amount', () => {
      expect(validatePaymentAmount(50)).toBe(true)
      expect(validatePaymentAmount(49)).toBe(false)
      expect(validatePaymentAmount(0)).toBe(false)
      expect(validatePaymentAmount(-10)).toBe(false)
      expect(validatePaymentAmount(Infinity)).toBe(false)
      expect(validatePaymentAmount(NaN)).toBe(false)
    })

    test('generates unique payment IDs', () => {
      const id1 = generatePaymentId()
      const id2 = generatePaymentId()
      
      expect(id1).not.toBe(id2)
      expect(id1).toMatch(/^pay_[a-f0-9]{32}$/)
    })

    test('parses valid webhook payload', () => {
      const payload = JSON.stringify({ event: 'payment.completed', amount: 1000 })
      const parsed = parseWebhookPayload(payload)
      
      expect(parsed.event).toBe('payment.completed')
      expect(parsed.amount).toBe(1000)
    })

    test('handles invalid webhook payload', () => {
      const parsed = parseWebhookPayload('not valid json')
      
      expect(parsed).toBeNull()
    })
  })

  describe('Billing Cycles', () => {
    test('supports monthly and yearly', () => {
      expect(BILLING_CYCLES).toContain('monthly')
      expect(BILLING_CYCLES).toContain('yearly')
      expect(BILLING_CYCLES.length).toBe(2)
    })

    test('calculates yearly savings percentage', () => {
      const monthlyPrice = SUBSCRIPTION_PRICES.starter_monthly_usd
      const yearlyPrice = SUBSCRIPTION_PRICES.starter_yearly_usd
      const savingsPercentage = ((monthlyPrice * 12 - yearlyPrice) / (monthlyPrice * 12)) * 100
      
      expect(savingsPercentage).toBeCloseTo(16.67, 1)
    })
  })

  describe('Currency Support', () => {
    test('supports USD, GBP, EUR', () => {
      expect(SUPPORTED_CURRENCIES).toContain('usd')
      expect(SUPPORTED_CURRENCIES).toContain('gbp')
      expect(SUPPORTED_CURRENCIES).toContain('eur')
    })

    test('GBP prices are lower than USD', () => {
      const plans = ['starter', 'professional', 'enterprise']
      
      plans.forEach(plan => {
        const usdPrice = SUBSCRIPTION_PRICES[getProductKey(plan, 'monthly', 'usd')]
        const gbpPrice = SUBSCRIPTION_PRICES[getProductKey(plan, 'monthly', 'gbp')]
        
        expect(gbpPrice).toBeLessThan(usdPrice)
      })
    })
  })

  describe('Invoice Generation', () => {
    function generateInvoiceNumber() {
      const date = new Date()
      const year = date.getFullYear()
      const month = String(date.getMonth() + 1).padStart(2, '0')
      const random = crypto.randomBytes(4).toString('hex').toUpperCase()
      return `INV-${year}${month}-${random}`
    }

    test('generates invoice numbers with correct format', () => {
      const invoice = generateInvoiceNumber()
      
      expect(invoice).toMatch(/^INV-\d{6}-[A-F0-9]{8}$/)
    })

    test('generates unique invoice numbers', () => {
      const invoices = new Set()
      for (let i = 0; i < 100; i++) {
        invoices.add(generateInvoiceNumber())
      }
      
      expect(invoices.size).toBe(100)
    })
  })

  describe('Subscription Status', () => {
    const subscriptionStates = ['active', 'past_due', 'canceled', 'incomplete', 'trialing', 'unpaid']

    function isActiveSubscription(status) {
      return status === 'active' || status === 'trialing'
    }

    function requiresAttention(status) {
      return ['past_due', 'incomplete', 'unpaid'].includes(status)
    }

    test('identifies active subscriptions', () => {
      expect(isActiveSubscription('active')).toBe(true)
      expect(isActiveSubscription('trialing')).toBe(true)
      expect(isActiveSubscription('past_due')).toBe(false)
      expect(isActiveSubscription('canceled')).toBe(false)
    })

    test('identifies subscriptions requiring attention', () => {
      expect(requiresAttention('past_due')).toBe(true)
      expect(requiresAttention('incomplete')).toBe(true)
      expect(requiresAttention('unpaid')).toBe(true)
      expect(requiresAttention('active')).toBe(false)
    })
  })

  describe('Refund Calculations', () => {
    function calculateProratedRefund(subscriptionPrice, daysRemaining, daysInPeriod) {
      return Math.round((subscriptionPrice * daysRemaining) / daysInPeriod)
    }

    test('calculates prorated refund for mid-cycle cancellation', () => {
      const price = 750
      const daysRemaining = 15
      const daysInPeriod = 30
      
      const refund = calculateProratedRefund(price, daysRemaining, daysInPeriod)
      
      expect(refund).toBe(375)
    })

    test('no refund at end of cycle', () => {
      const price = 750
      const daysRemaining = 0
      const daysInPeriod = 30
      
      const refund = calculateProratedRefund(price, daysRemaining, daysInPeriod)
      
      expect(refund).toBe(0)
    })

    test('full refund at start of cycle', () => {
      const price = 750
      const daysRemaining = 30
      const daysInPeriod = 30
      
      const refund = calculateProratedRefund(price, daysRemaining, daysInPeriod)
      
      expect(refund).toBe(750)
    })
  })

  describe('Usage Events', () => {
    const DODO_USAGE_EVENT_NAME = 'token.usage'
    const DODO_USAGE_EVENT_COST_FIELD = 'costCents'
    const DODO_USAGE_EVENT_TOKENS_FIELD = 'billableTokens'

    function createUsageEvent(userId, tokens, costCents) {
      return {
        event: DODO_USAGE_EVENT_NAME,
        customerId: userId,
        properties: {
          [DODO_USAGE_EVENT_TOKENS_FIELD]: tokens,
          [DODO_USAGE_EVENT_COST_FIELD]: costCents
        },
        timestamp: Date.now()
      }
    }

    test('creates usage event with correct structure', () => {
      const event = createUsageEvent('user-123', 50000, 12.5)
      
      expect(event.event).toBe('token.usage')
      expect(event.customerId).toBe('user-123')
      expect(event.properties.billableTokens).toBe(50000)
      expect(event.properties.costCents).toBe(12.5)
      expect(event.timestamp).toBeDefined()
    })

    test('event name is correctly configured', () => {
      expect(DODO_USAGE_EVENT_NAME).toBe('token.usage')
    })

    test('usage event fields are defined', () => {
      expect(DODO_USAGE_EVENT_COST_FIELD).toBe('costCents')
      expect(DODO_USAGE_EVENT_TOKENS_FIELD).toBe('billableTokens')
    })
  })
})

describe('Webhook Handling', () => {
  function verifyWebhookSignature(payload, signature, secret) {
    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(payload)
      .digest('hex')
    return signature === `sha256=${expectedSignature}`
  }

  const testSecret = 'webhook-secret-key'

  test('verifies valid webhook signature', () => {
    const payload = JSON.stringify({ event: 'test' })
    const signature = `sha256=${crypto.createHmac('sha256', testSecret).update(payload).digest('hex')}`
    
    expect(verifyWebhookSignature(payload, signature, testSecret)).toBe(true)
  })

  test('rejects invalid webhook signature', () => {
    const payload = JSON.stringify({ event: 'test' })
    const signature = 'sha256=invalid'
    
    expect(verifyWebhookSignature(payload, signature, testSecret)).toBe(false)
  })

  test('rejects tampered payload', () => {
    const payload = JSON.stringify({ event: 'test' })
    const signature = `sha256=${crypto.createHmac('sha256', testSecret).update(payload).digest('hex')}`
    const tamperedPayload = JSON.stringify({ event: 'tampered' })
    
    expect(verifyWebhookSignature(tamperedPayload, signature, testSecret)).toBe(false)
  })

  test('handles payment completed webhook', () => {
    const webhookPayload = {
      event: 'payment.completed',
      data: {
        payment_id: 'pay_123',
        amount: 750,
        currency: 'usd',
        customer_id: 'user_456'
      }
    }
    
    expect(webhookPayload.event).toBe('payment.completed')
    expect(webhookPayload.data.amount).toBe(750)
  })

  test('handles subscription created webhook', () => {
    const webhookPayload = {
      event: 'subscription.created',
      data: {
        subscription_id: 'sub_123',
        customer_id: 'user_456',
        plan: 'professional',
        status: 'active'
      }
    }
    
    expect(webhookPayload.event).toBe('subscription.created')
    expect(webhookPayload.data.plan).toBe('professional')
  })

  test('handles subscription cancelled webhook', () => {
    const webhookPayload = {
      event: 'subscription.cancelled',
      data: {
        subscription_id: 'sub_123',
        customer_id: 'user_456',
        cancelled_at: Date.now()
      }
    }
    
    expect(webhookPayload.event).toBe('subscription.cancelled')
  })
})
