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

const DODO_BASE_URL_TEST = 'https://test.dodopayments.com'
const DODO_BASE_URL_LIVE = 'https://live.dodopayments.com'

const SUBSCRIPTION_PRICES = {
  starter_monthly_usd: 750,
  starter_yearly_usd: 7500,
  professional_monthly_usd: 2500,
  professional_yearly_usd: 25000,
  enterprise_monthly_usd: 7500,
  enterprise_yearly_usd: 75000,
}

const TOPUP_PRICES = {
  topup_1_usd: 750,
  topup_2_usd: 2500,
  topup_3_usd: 7500,
  topup_4_usd: 12500,
}

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

const BUSINESS_TOPUP_DISCOUNT = 0.025
const ENTERPRISE_TOPUP_DISCOUNT = 0.05
const MIN_PAYMENT_AMOUNT = 50

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

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

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

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

function calculateProratedRefund(subscriptionPrice, daysRemaining, daysInPeriod) {
  return Math.round((subscriptionPrice * daysRemaining) / daysInPeriod)
}

describe('Dodo Payments Integration', () => {
  describe('Environment Configuration', () => {
    test('selects correct base URL for test environment', () => {
      const DODO_ENVIRONMENT = 'test'
      const baseUrl = DODO_ENVIRONMENT.includes('live') ? DODO_BASE_URL_LIVE : DODO_BASE_URL_TEST
      expect(baseUrl).toBe(DODO_BASE_URL_TEST)
    })

    test('selects correct base URL for live environment', () => {
      const DODO_ENVIRONMENT = 'live'
      const baseUrl = DODO_ENVIRONMENT.includes('live') ? DODO_BASE_URL_LIVE : DODO_BASE_URL_TEST
      expect(baseUrl).toBe(DODO_BASE_URL_LIVE)
    })

    test('defaults to test environment', () => {
      const DODO_ENVIRONMENT = ''
      const baseUrl = DODO_ENVIRONMENT.includes('live') ? DODO_BASE_URL_LIVE : DODO_BASE_URL_TEST
      expect(baseUrl).toBe(DODO_BASE_URL_TEST)
    })
  })

  describe('Checkout Flow', () => {
    function createCheckoutSession(options) {
      const {
        productId,
        customerId,
        successUrl,
        cancelUrl,
        metadata = {},
      } = options

      return {
        id: `cs_${crypto.randomBytes(16).toString('hex')}`,
        productId,
        customerId,
        successUrl,
        cancelUrl,
        metadata,
        status: 'open',
        createdAt: Date.now(),
      }
    }

    test('creates checkout session with required fields', () => {
      const session = createCheckoutSession({
        productId: 'prod_starter_monthly_usd',
        customerId: 'user-123',
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
      })

      expect(session.id).toMatch(/^cs_[a-f0-9]{32}$/)
      expect(session.productId).toBe('prod_starter_monthly_usd')
      expect(session.status).toBe('open')
    })

    test('includes metadata in checkout session', () => {
      const session = createCheckoutSession({
        productId: 'prod_topup_1_usd',
        customerId: 'user-456',
        successUrl: 'https://example.com/success',
        cancelUrl: 'https://example.com/cancel',
        metadata: {
          tokens: TOPUP_TOKENS.topup_1,
          userId: 'user-456',
        },
      })

      expect(session.metadata.tokens).toBe(100_000)
      expect(session.metadata.userId).toBe('user-456')
    })

    test('validates minimum payment amount', () => {
      expect(MIN_PAYMENT_AMOUNT).toBe(50)
      expect(TOPUP_PRICES.topup_1_usd).toBeGreaterThanOrEqual(MIN_PAYMENT_AMOUNT)
    })

    test('generates unique checkout session IDs', () => {
      const ids = new Set()
      for (let i = 0; i < 100; i++) {
        const session = createCheckoutSession({
          productId: 'prod_test',
          customerId: 'user-1',
          successUrl: 'https://example.com/success',
          cancelUrl: 'https://example.com/cancel',
        })
        ids.add(session.id)
      }
      expect(ids.size).toBe(100)
    })
  })

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

    const testSecret = 'dodo_webhook_secret_key'

    function processWebhook(payload) {
      const { event, data } = payload

      switch (event) {
        case 'payment.completed':
          return { processed: true, type: 'payment', paymentId: data.payment_id }
        case 'subscription.created':
          return { processed: true, type: 'subscription', subscriptionId: data.subscription_id }
        case 'subscription.cancelled':
          return { processed: true, type: 'cancellation', subscriptionId: data.subscription_id }
        case 'subscription.renewed':
          return { processed: true, type: 'renewal', subscriptionId: data.subscription_id }
        default:
          return { processed: false, reason: 'Unknown event type' }
      }
    }

    test('verifies valid webhook signature', () => {
      const payload = JSON.stringify({ event: 'payment.completed', data: {} })
      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: 'payment.completed', data: {} })
      const signature = 'sha256=invalid_signature'

      expect(verifyWebhookSignature(payload, signature, testSecret)).toBe(false)
    })

    test('detects tampered payload', () => {
      const payload = JSON.stringify({ event: 'payment.completed', data: { amount: 100 } })
      const signature = `sha256=${crypto.createHmac('sha256', testSecret).update(payload).digest('hex')}`
      const tamperedPayload = JSON.stringify({ event: 'payment.completed', data: { amount: 999 } })

      expect(verifyWebhookSignature(tamperedPayload, signature, testSecret)).toBe(false)
    })

    test('processes payment.completed webhook', () => {
      const payload = {
        event: 'payment.completed',
        data: {
          payment_id: 'pay_123',
          amount: 750,
          currency: 'usd',
          customer_id: 'user_456',
          product_id: 'prod_topup_1_usd',
        },
      }

      const result = processWebhook(payload)
      expect(result.processed).toBe(true)
      expect(result.type).toBe('payment')
      expect(result.paymentId).toBe('pay_123')
    })

    test('processes subscription.created webhook', () => {
      const payload = {
        event: 'subscription.created',
        data: {
          subscription_id: 'sub_123',
          customer_id: 'user_456',
          product_id: 'prod_starter_monthly_usd',
          status: 'active',
          current_period_start: Date.now(),
          current_period_end: Date.now() + 30 * 24 * 60 * 60 * 1000,
        },
      }

      const result = processWebhook(payload)
      expect(result.processed).toBe(true)
      expect(result.type).toBe('subscription')
    })

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

      const result = processWebhook(payload)
      expect(result.processed).toBe(true)
      expect(result.type).toBe('cancellation')
    })

    test('handles unknown webhook event types', () => {
      const payload = {
        event: 'unknown.event',
        data: {},
      }

      const result = processWebhook(payload)
      expect(result.processed).toBe(false)
      expect(result.reason).toBe('Unknown event type')
    })
  })

  describe('Subscription Lifecycle', () => {
    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)
    }

    function getSubscriptionAction(status) {
      if (status === 'active') return 'grant_access'
      if (status === 'trialing') return 'grant_access'
      if (status === 'past_due') return 'notify_user'
      if (status === 'canceled') return 'revoke_access'
      if (status === 'unpaid') return 'revoke_access'
      return 'review'
    }

    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)
    })

    test('determines correct subscription action', () => {
      expect(getSubscriptionAction('active')).toBe('grant_access')
      expect(getSubscriptionAction('trialing')).toBe('grant_access')
      expect(getSubscriptionAction('past_due')).toBe('notify_user')
      expect(getSubscriptionAction('canceled')).toBe('revoke_access')
      expect(getSubscriptionAction('unpaid')).toBe('revoke_access')
    })

    test('calculates subscription renewal date', () => {
      const now = Date.now()
      const billingCycle = 'monthly'
      const renewalDate = billingCycle === 'monthly' 
        ? now + 30 * 24 * 60 * 60 * 1000 
        : now + 365 * 24 * 60 * 60 * 1000

      expect(renewalDate).toBeGreaterThan(now)
    })
  })

  describe('Top-up Processing', () => {
    function processTopup(userId, topupOption, currency = 'usd') {
      const productKey = getTopupProductKey(topupOption, currency)
      const price = TOPUP_PRICES[productKey]
      const tokens = TOPUP_TOKENS[`topup_${topupOption}`]

      return {
        userId,
        productId: productKey,
        price,
        tokens,
        currency,
        status: 'pending',
        createdAt: Date.now(),
      }
    }

    test('processes top-up option 1 correctly', () => {
      const result = processTopup('user-1', '1', 'usd')

      expect(result.price).toBe(750)
      expect(result.tokens).toBe(100_000)
      expect(result.productId).toBe('topup_1_usd')
    })

    test('processes top-up option 4 correctly', () => {
      const result = processTopup('user-1', '4', 'usd')

      expect(result.price).toBe(12500)
      expect(result.tokens).toBe(50_000_000)
    })

    test('applies business discount', () => {
      const originalPrice = TOPUP_PRICES.topup_2_usd
      const discountedPrice = applyDiscount(originalPrice, BUSINESS_TOPUP_DISCOUNT)

      expect(discountedPrice).toBe(2438)
    })

    test('applies enterprise discount', () => {
      const originalPrice = TOPUP_PRICES.topup_2_usd
      const discountedPrice = applyDiscount(originalPrice, ENTERPRISE_TOPUP_DISCOUNT)

      expect(discountedPrice).toBe(2375)
    })

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

    test('calculates price per token', () => {
      const pricePerToken1 = TOPUP_PRICES.topup_1_usd / TOPUP_TOKENS.topup_1
      const pricePerToken4 = TOPUP_PRICES.topup_4_usd / TOPUP_TOKENS.topup_4

      expect(pricePerToken1).toBeCloseTo(0.0075, 4)
      expect(pricePerToken4).toBeCloseTo(0.00025, 4)
      expect(pricePerToken4).toBeLessThan(pricePerToken1)
    })
  })

  describe('Pay-as-you-go Billing', () => {
    const PAYG_PRICES = {
      usd: 250,
      gbp: 200,
      eur: 250,
    }
    const PAYG_UNIT_TOKENS = 1_000_000

    function calculatePaygCost(tokens, currency = 'usd') {
      const rate = PAYG_PRICES[currency] || PAYG_PRICES.usd
      return (tokens / PAYG_UNIT_TOKENS) * rate
    }

    test('calculates PAYG cost for USD', () => {
      const cost = calculatePaygCost(500_000, 'usd')
      expect(cost).toBe(125)
    })

    test('calculates PAYG cost for GBP', () => {
      const cost = calculatePaygCost(1_000_000, 'gbp')
      expect(cost).toBe(200)
    })

    test('handles zero tokens', () => {
      const cost = calculatePaygCost(0, 'usd')
      expect(cost).toBe(0)
    })

    test('defaults to USD for unknown currency', () => {
      const cost = calculatePaygCost(1_000_000, 'jpy')
      expect(cost).toBe(250)
    })
  })

  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', 50_000, 12.5)

      expect(event.event).toBe('token.usage')
      expect(event.customerId).toBe('user-123')
      expect(event.properties.billableTokens).toBe(50_000)
      expect(event.properties.costCents).toBe(12.5)
      expect(event.timestamp).toBeDefined()
    })

    test('event name matches configuration', () => {
      expect(DODO_USAGE_EVENT_NAME).toBe('token.usage')
    })

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

  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}`
    }

    function createInvoice(options) {
      const {
        customerId,
        items,
        currency = 'usd',
        status = 'draft',
      } = options

      const total = items.reduce((sum, item) => sum + item.amount, 0)

      return {
        id: generateInvoiceNumber(),
        customerId,
        items,
        currency,
        total,
        status,
        createdAt: Date.now(),
        dueAt: Date.now() + 30 * 24 * 60 * 60 * 1000,
      }
    }

    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)
    })

    test('creates invoice with items', () => {
      const invoice = createInvoice({
        customerId: 'user-123',
        items: [
          { description: 'Starter Plan - Monthly', amount: 750 },
          { description: 'Token Top-up', amount: 2500 },
        ],
      })

      expect(invoice.items.length).toBe(2)
      expect(invoice.total).toBe(3250)
      expect(invoice.status).toBe('draft')
    })

    test('calculates invoice total correctly', () => {
      const invoice = createInvoice({
        customerId: 'user-456',
        items: [
          { description: 'Professional Plan', amount: 2500 },
          { description: 'Add-on', amount: 500 },
        ],
      })

      expect(invoice.total).toBe(3000)
    })
  })

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

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

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

    test('handles yearly subscription refund', () => {
      const refund = calculateProratedRefund(7500, 180, 365)
      expect(refund).toBeCloseTo(3699, 0)
    })
  })

  describe('Product ID Resolution', () => {
    const SUBSCRIPTION_PRODUCT_IDS = {
      starter_monthly_usd: 'prod_starter_monthly_usd',
      starter_yearly_usd: 'prod_starter_yearly_usd',
      professional_monthly_usd: 'prod_professional_monthly_usd',
      enterprise_monthly_usd: 'prod_enterprise_monthly_usd',
    }

    test('resolves subscription product ID', () => {
      const key = getProductKey('starter', 'monthly', 'usd')
      expect(SUBSCRIPTION_PRODUCT_IDS[key]).toBe('prod_starter_monthly_usd')
    })

    test('resolves top-up product ID', () => {
      const key = getTopupProductKey('1', 'usd')
      expect(key).toBe('topup_1_usd')
    })

    test('generates product key for all combinations', () => {
      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(key).toBe(`${plan}_${cycle}_${currency}`)
          })
        })
      })
    })
  })

  describe('Currency Handling', () => {
    const SUPPORTED_CURRENCIES = ['usd', 'gbp', 'eur']

    test('supports all required currencies', () => {
      expect(SUPPORTED_CURRENCIES).toContain('usd')
      expect(SUPPORTED_CURRENCIES).toContain('gbp')
      expect(SUPPORTED_CURRENCIES).toContain('eur')
    })

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

    test('defaults to $ for unknown currency', () => {
      expect(formatPrice(100, 'jpy')).toBe('$1.00')
    })
  })
})
