import { describe, it, expect } from 'vitest'
import { DROPS_PER_ML, VOLUME_DROPS } from '../stores/oils'

// ---------------------------------------------------------------------------
// Replicate the volume / dilution calculation logic locally for unit testing
// ---------------------------------------------------------------------------

function getTotalDropsForMode(mode, customVal = 0, customUnit = 'drops') {
  if (mode === 'single') return null
  if (mode === 'custom') {
    return customUnit === 'ml' ? Math.round(customVal * 20) : Math.round(customVal)
  }
  const presets = { '5ml': 100, '10ml': 200, '30ml': 600 }
  return presets[mode] || 100
}

function applyVolume(ingredients, mode, ratio, customVal, customUnit) {
  let targetEO, targetCoconut
  if (mode === 'single') {
    targetCoconut = 10
    targetEO = Math.round(targetCoconut / ratio)
  } else {
    const totalDrops = getTotalDropsForMode(mode, customVal, customUnit)
    if (!totalDrops || totalDrops <= 0) return null
    targetEO = Math.round(totalDrops / (1 + ratio))
    targetCoconut = totalDrops - targetEO
  }

  const eos = ingredients.filter(i => i.oil !== '椰子油')
  const currentTotalEO = eos.reduce((s, i) => s + i.drops, 0)
  if (currentTotalEO === 0) return null

  const factor = targetEO / currentTotalEO
  const scaled = eos.map(ing => ({
    oil: ing.oil,
    drops: Math.max(0.5, Math.round(ing.drops * factor * 2) / 2),
  }))
  scaled.push({ oil: '椰子油', drops: targetCoconut })
  return scaled
}

function detectVolumeMode(ingredients) {
  const eos = ingredients.filter(i => i.oil !== '椰子油')
  const coconut = ingredients.find(i => i.oil === '椰子油')
  const totalEO = eos.reduce((s, i) => s + i.drops, 0)
  const cDrops = coconut ? coconut.drops : 0
  const totalAll = totalEO + cDrops
  if (totalAll === 100) return '5ml'
  if (totalAll === 200) return '10ml'
  if (totalAll === 600) return '30ml'
  if (cDrops > 0 && cDrops <= 20 && totalAll <= 40) return 'single'
  if (cDrops > 0) return 'custom'
  return 'single'
}

function getDilutionRatio(ingredients) {
  const eos = ingredients.filter(i => i.oil !== '椰子油')
  const coconut = ingredients.find(i => i.oil === '椰子油')
  const totalEO = eos.reduce((s, i) => s + i.drops, 0)
  const cDrops = coconut ? coconut.drops : 0
  if (totalEO > 0 && cDrops > 0) return Math.round(cDrops / totalEO)
  return 0
}

// ---------------------------------------------------------------------------
// Helper: sum EO drops from a result set
// ---------------------------------------------------------------------------
function sumEO(result) {
  return result.filter(i => i.oil !== '椰子油').reduce((s, i) => s + i.drops, 0)
}

function coconutDrops(result) {
  const c = result.find(i => i.oil === '椰子油')
  return c ? c.drops : 0
}

// ===========================================================================
// Tests
// ===========================================================================

describe('Volume Constants', () => {
  it('DROPS_PER_ML equals 18.6', () => {
    expect(DROPS_PER_ML).toBe(18.6)
  })

  it('VOLUME_DROPS has standard doTERRA sizes', () => {
    expect(VOLUME_DROPS).toHaveProperty('2.5')
    expect(VOLUME_DROPS).toHaveProperty('5')
    expect(VOLUME_DROPS).toHaveProperty('10')
    expect(VOLUME_DROPS).toHaveProperty('15')
    expect(VOLUME_DROPS).toHaveProperty('115')
  })

  it('5ml bottle = 93 drops (factory standard)', () => {
    expect(VOLUME_DROPS['5']).toBe(93)
  })

  it('15ml bottle = 280 drops', () => {
    expect(VOLUME_DROPS['15']).toBe(280)
  })

  it('2.5ml bottle = 46 drops', () => {
    expect(VOLUME_DROPS['2.5']).toBe(46)
  })

  it('10ml bottle = 186 drops', () => {
    expect(VOLUME_DROPS['10']).toBe(186)
  })

  it('115ml bottle = 2146 drops', () => {
    expect(VOLUME_DROPS['115']).toBe(2146)
  })
})

describe('getTotalDropsForMode', () => {
  it("'single' returns null", () => {
    expect(getTotalDropsForMode('single')).toBeNull()
  })

  it("'5ml' returns 100", () => {
    expect(getTotalDropsForMode('5ml')).toBe(100)
  })

  it("'10ml' returns 200", () => {
    expect(getTotalDropsForMode('10ml')).toBe(200)
  })

  it("'30ml' returns 600", () => {
    expect(getTotalDropsForMode('30ml')).toBe(600)
  })

  it("'custom' with 20ml returns 400", () => {
    expect(getTotalDropsForMode('custom', 20, 'ml')).toBe(400)
  })

  it("'custom' with 15 drops returns 15", () => {
    expect(getTotalDropsForMode('custom', 15, 'drops')).toBe(15)
  })

  it("'custom' with 0 ml returns 0", () => {
    expect(getTotalDropsForMode('custom', 0, 'ml')).toBe(0)
  })

  it("'custom' rounds fractional ml values", () => {
    expect(getTotalDropsForMode('custom', 7.5, 'ml')).toBe(150)
  })

  it('unknown mode falls back to 100', () => {
    expect(getTotalDropsForMode('unknown')).toBe(100)
  })
})

describe('applyVolume - single dose', () => {
  const baseRecipe = [
    { oil: '薰衣草', drops: 5 },
    { oil: '椰子油', drops: 10 },
  ]

  it('with ratio 10, coconut=10, EO=1', () => {
    const result = applyVolume(baseRecipe, 'single', 10)
    expect(coconutDrops(result)).toBe(10)
    expect(sumEO(result)).toBe(1)
  })

  it('with ratio 5, coconut=10, EO=2', () => {
    const result = applyVolume(baseRecipe, 'single', 5)
    expect(coconutDrops(result)).toBe(10)
    expect(sumEO(result)).toBe(2)
  })

  it('scales 3 oils proportionally', () => {
    const threeOils = [
      { oil: '薰衣草', drops: 6 },
      { oil: '乳香', drops: 3 },
      { oil: '薄荷', drops: 3 },
      { oil: '椰子油', drops: 10 },
    ]
    const result = applyVolume(threeOils, 'single', 5)
    // targetEO = round(10/5) = 2
    // factor = 2/12
    const lavender = result.find(i => i.oil === '薰衣草')
    const frank = result.find(i => i.oil === '乳香')
    const mint = result.find(i => i.oil === '薄荷')
    // Lavender should get ~half of the EO, frank and mint ~quarter each
    expect(lavender.drops).toBeGreaterThanOrEqual(frank.drops)
    expect(frank.drops).toBe(mint.drops)
  })

  it('minimum 0.5 drops per oil', () => {
    const tinyOil = [
      { oil: '薰衣草', drops: 1 },
      { oil: '乳香', drops: 1 },
      { oil: '椰子油', drops: 10 },
    ]
    // ratio 20 → targetEO = round(10/20) = 1, factor = 0.5
    // each oil: max(0.5, round(1*0.5*2)/2) = max(0.5, 0.5) = 0.5
    const result = applyVolume(tinyOil, 'single', 20)
    result.filter(i => i.oil !== '椰子油').forEach(i => {
      expect(i.drops).toBeGreaterThanOrEqual(0.5)
    })
  })
})

describe('applyVolume - 5ml bottle', () => {
  it('100 total drops with ratio 10: EO~9, coconut~91', () => {
    const recipe = [
      { oil: '薰衣草', drops: 3 },
      { oil: '椰子油', drops: 10 },
    ]
    const result = applyVolume(recipe, '5ml', 10)
    const totalEO = sumEO(result)
    const coco = coconutDrops(result)
    // targetEO = round(100/11) = 9, coconut = 91
    expect(totalEO).toBe(9)
    expect(coco).toBe(91)
    expect(totalEO + coco).toBe(100)
  })

  it('scales existing recipe proportionally', () => {
    const recipe = [
      { oil: '薰衣草', drops: 6 },
      { oil: '乳香', drops: 3 },
      { oil: '椰子油', drops: 10 },
    ]
    const result = applyVolume(recipe, '5ml', 10)
    const lav = result.find(i => i.oil === '薰衣草')
    const frank = result.find(i => i.oil === '乳香')
    // Original ratio is 2:1, scaled should preserve ~2:1
    expect(lav.drops).toBeGreaterThan(frank.drops)
  })

  it('preserves oil ratios approximately', () => {
    const recipe = [
      { oil: '薰衣草', drops: 10 },
      { oil: '乳香', drops: 5 },
      { oil: '椰子油', drops: 20 },
    ]
    const result = applyVolume(recipe, '5ml', 10)
    const lav = result.find(i => i.oil === '薰衣草')
    const frank = result.find(i => i.oil === '乳香')
    // ratio should be close to 2:1
    expect(lav.drops / frank.drops).toBeCloseTo(2, 0)
  })
})

describe('applyVolume - 10ml bottle', () => {
  const recipe = [
    { oil: '薰衣草', drops: 5 },
    { oil: '椰子油', drops: 10 },
  ]

  it('produces 200 total drops', () => {
    const result = applyVolume(recipe, '10ml', 10)
    const total = sumEO(result) + coconutDrops(result)
    expect(total).toBe(200)
  })

  it('ratio 5 gives ~33 EO drops', () => {
    const result = applyVolume(recipe, '10ml', 5)
    // targetEO = round(200/6) = 33
    expect(sumEO(result)).toBe(33)
  })

  it('ratio 10 gives ~18 EO drops', () => {
    const result = applyVolume(recipe, '10ml', 10)
    // targetEO = round(200/11) = 18
    expect(sumEO(result)).toBe(18)
  })

  it('ratio 15 gives ~13 EO drops', () => {
    const result = applyVolume(recipe, '10ml', 15)
    // targetEO = round(200/16) = 13 (12.5 rounds to 13)
    expect(sumEO(result)).toBe(13)
  })
})

describe('applyVolume - 30ml bottle', () => {
  const recipe = [
    { oil: '薰衣草', drops: 5 },
    { oil: '乳香', drops: 3 },
    { oil: '椰子油', drops: 20 },
  ]

  it('produces 600 total drops', () => {
    const result = applyVolume(recipe, '30ml', 10)
    const total = sumEO(result) + coconutDrops(result)
    expect(total).toBe(600)
  })

  it('large recipe scaling preserves ratios', () => {
    const result = applyVolume(recipe, '30ml', 10)
    const lav = result.find(i => i.oil === '薰衣草')
    const frank = result.find(i => i.oil === '乳香')
    // Original ratio 5:3 ≈ 1.67
    expect(lav.drops / frank.drops).toBeCloseTo(5 / 3, 0)
  })

  it('ratio 10 gives ~55 EO drops', () => {
    const result = applyVolume(recipe, '30ml', 10)
    // targetEO = round(600/11) = 55
    expect(sumEO(result)).toBe(55)
  })
})

describe('applyVolume - custom', () => {
  const recipe = [
    { oil: '薰衣草', drops: 5 },
    { oil: '椰子油', drops: 10 },
  ]

  it('custom 20ml = 400 total drops', () => {
    const result = applyVolume(recipe, 'custom', 10, 20, 'ml')
    const total = sumEO(result) + coconutDrops(result)
    expect(total).toBe(400)
  })

  it('custom 50 drops total', () => {
    const result = applyVolume(recipe, 'custom', 10, 50, 'drops')
    const total = sumEO(result) + coconutDrops(result)
    expect(total).toBe(50)
  })

  it('custom 0 ml returns null', () => {
    const result = applyVolume(recipe, 'custom', 10, 0, 'ml')
    expect(result).toBeNull()
  })
})

describe('applyVolume - edge cases', () => {
  it('empty ingredients returns null', () => {
    const result = applyVolume([], '5ml', 10)
    expect(result).toBeNull()
  })

  it('only coconut oil (no EO) returns null', () => {
    const result = applyVolume([{ oil: '椰子油', drops: 10 }], '5ml', 10)
    expect(result).toBeNull()
  })

  it('single oil scales correctly', () => {
    const recipe = [
      { oil: '薰衣草', drops: 5 },
      { oil: '椰子油', drops: 10 },
    ]
    const result = applyVolume(recipe, '5ml', 10)
    expect(result).not.toBeNull()
    expect(result.filter(i => i.oil !== '椰子油')).toHaveLength(1)
  })

  it('very small drops round to 0.5 minimum', () => {
    const recipe = [
      { oil: '薰衣草', drops: 100 },
      { oil: '乳香', drops: 1 },
      { oil: '椰子油', drops: 10 },
    ]
    // Single mode ratio 50 → targetEO = round(10/50) = 0 → but round gives 0
    // Actually ratio 10 → targetEO = 1, factor = 1/101
    // 乳香: max(0.5, round(1 * (1/101) * 2)/2) = max(0.5, 0) = 0.5
    const result = applyVolume(recipe, 'single', 10)
    const frank = result.find(i => i.oil === '乳香')
    expect(frank.drops).toBe(0.5)
  })

  it('coconut oil is always the last element', () => {
    const recipe = [
      { oil: '椰子油', drops: 10 },
      { oil: '薰衣草', drops: 5 },
      { oil: '乳香', drops: 3 },
    ]
    const result = applyVolume(recipe, '5ml', 10)
    expect(result[result.length - 1].oil).toBe('椰子油')
  })

  it('no coconut in input still adds coconut to output', () => {
    const recipe = [
      { oil: '薰衣草', drops: 5 },
      { oil: '乳香', drops: 3 },
    ]
    const result = applyVolume(recipe, '5ml', 10)
    expect(result.find(i => i.oil === '椰子油')).toBeDefined()
  })
})

describe('detectVolumeMode', () => {
  it('100 total drops → 5ml', () => {
    const ing = [
      { oil: '薰衣草', drops: 10 },
      { oil: '椰子油', drops: 90 },
    ]
    expect(detectVolumeMode(ing)).toBe('5ml')
  })

  it('200 total drops → 10ml', () => {
    const ing = [
      { oil: '薰衣草', drops: 20 },
      { oil: '椰子油', drops: 180 },
    ]
    expect(detectVolumeMode(ing)).toBe('10ml')
  })

  it('600 total drops → 30ml', () => {
    const ing = [
      { oil: '薰衣草', drops: 50 },
      { oil: '椰子油', drops: 550 },
    ]
    expect(detectVolumeMode(ing)).toBe('30ml')
  })

  it('small recipe with coconut → single', () => {
    const ing = [
      { oil: '薰衣草', drops: 2 },
      { oil: '椰子油', drops: 10 },
    ]
    expect(detectVolumeMode(ing)).toBe('single')
  })

  it('coconut <= 20 and total <= 40 → single', () => {
    const ing = [
      { oil: '薰衣草', drops: 20 },
      { oil: '椰子油', drops: 20 },
    ]
    expect(detectVolumeMode(ing)).toBe('single')
  })

  it('coconut > 20 but not a preset → custom', () => {
    const ing = [
      { oil: '薰衣草', drops: 10 },
      { oil: '椰子油', drops: 40 },
    ]
    expect(detectVolumeMode(ing)).toBe('custom')
  })

  it('total > 40 but not a preset → custom', () => {
    const ing = [
      { oil: '薰衣草', drops: 30 },
      { oil: '椰子油', drops: 20 },
    ]
    expect(detectVolumeMode(ing)).toBe('custom')
  })

  it('no coconut at all → single', () => {
    const ing = [{ oil: '薰衣草', drops: 5 }]
    expect(detectVolumeMode(ing)).toBe('single')
  })

  it('only EO totalling 100 still detects 5ml', () => {
    const ing = [{ oil: '薰衣草', drops: 100 }]
    expect(detectVolumeMode(ing)).toBe('5ml')
  })
})

describe('getDilutionRatio', () => {
  it('standard 1:10 ratio', () => {
    const ing = [
      { oil: '薰衣草', drops: 10 },
      { oil: '椰子油', drops: 100 },
    ]
    expect(getDilutionRatio(ing)).toBe(10)
  })

  it('no coconut returns 0', () => {
    const ing = [{ oil: '薰衣草', drops: 5 }]
    expect(getDilutionRatio(ing)).toBe(0)
  })

  it('no EO returns 0', () => {
    const ing = [{ oil: '椰子油', drops: 50 }]
    expect(getDilutionRatio(ing)).toBe(0)
  })

  it('1:5 ratio', () => {
    const ing = [
      { oil: '薰衣草', drops: 10 },
      { oil: '椰子油', drops: 50 },
    ]
    expect(getDilutionRatio(ing)).toBe(5)
  })

  it('1:1 ratio', () => {
    const ing = [
      { oil: '薰衣草', drops: 10 },
      { oil: '椰子油', drops: 10 },
    ]
    expect(getDilutionRatio(ing)).toBe(1)
  })

  it('rounds to nearest integer', () => {
    const ing = [
      { oil: '薰衣草', drops: 3 },
      { oil: '椰子油', drops: 20 },
    ]
    // 20/3 = 6.67 → rounds to 7
    expect(getDilutionRatio(ing)).toBe(7)
  })

  it('multiple EO oils summed for ratio', () => {
    const ing = [
      { oil: '薰衣草', drops: 5 },
      { oil: '乳香', drops: 5 },
      { oil: '椰子油', drops: 100 },
    ]
    // 100/10 = 10
    expect(getDilutionRatio(ing)).toBe(10)
  })
})

describe('Real recipe scaling', () => {
  const baseRecipe = [
    { oil: '薰衣草', drops: 6 },
    { oil: '乳香', drops: 3 },
    { oil: '薄荷', drops: 3 },
    { oil: '椰子油', drops: 20 },
  ]

  it('scale to 5ml preserves approximate proportions', () => {
    const result = applyVolume(baseRecipe, '5ml', 10)
    const lav = result.find(i => i.oil === '薰衣草').drops
    const frank = result.find(i => i.oil === '乳香').drops
    const mint = result.find(i => i.oil === '薄荷').drops
    // Original: lav is 2x frank and 2x mint; frank == mint
    expect(frank).toBe(mint)
    expect(lav).toBeGreaterThanOrEqual(frank)
  })

  it('scale to 10ml preserves approximate proportions', () => {
    const result = applyVolume(baseRecipe, '10ml', 10)
    const lav = result.find(i => i.oil === '薰衣草').drops
    const frank = result.find(i => i.oil === '乳香').drops
    const mint = result.find(i => i.oil === '薄荷').drops
    expect(frank).toBe(mint)
    expect(lav).toBeGreaterThanOrEqual(frank)
  })

  it('10ml has approximately 2x the EO drops of 5ml', () => {
    const result5 = applyVolume(baseRecipe, '5ml', 10)
    const result10 = applyVolume(baseRecipe, '10ml', 10)
    const eo5 = sumEO(result5)
    const eo10 = sumEO(result10)
    // 10ml target = round(200/11) = 18, 5ml target = round(100/11) = 9
    expect(eo10 / eo5).toBeCloseTo(2, 0)
  })

  it('30ml has approximately 3x the EO drops of 10ml', () => {
    const result10 = applyVolume(baseRecipe, '10ml', 10)
    const result30 = applyVolume(baseRecipe, '30ml', 10)
    const eo10 = sumEO(result10)
    const eo30 = sumEO(result30)
    expect(eo30 / eo10).toBeCloseTo(3, 0)
  })

  it('scale up then scale down gives close to original EO count', () => {
    // Scale to 30ml
    const scaled30 = applyVolume(baseRecipe, '30ml', 10)
    // Now scale the 30ml result back to single
    const scaledBack = applyVolume(scaled30, 'single', 10)
    // Single: targetEO = round(10/10) = 1
    const totalEOBack = sumEO(scaledBack)
    expect(totalEOBack).toBeGreaterThanOrEqual(1)
    expect(totalEOBack).toBeLessThanOrEqual(3) // small due to rounding
  })

  it('all EO drops are multiples of 0.5', () => {
    const result = applyVolume(baseRecipe, '5ml', 10)
    result.filter(i => i.oil !== '椰子油').forEach(i => {
      expect(i.drops * 2).toBe(Math.round(i.drops * 2))
    })
  })

  it('coconut drops are always a whole number', () => {
    const result = applyVolume(baseRecipe, '10ml', 10)
    const coco = coconutDrops(result)
    expect(coco).toBe(Math.round(coco))
  })

  it('total drops are within 1 drop of the volume preset (0.5 rounding)', () => {
    ;['5ml', '10ml', '30ml'].forEach(mode => {
      const presets = { '5ml': 100, '10ml': 200, '30ml': 600 }
      const result = applyVolume(baseRecipe, mode, 10)
      const total = sumEO(result) + coconutDrops(result)
      // EO drops are rounded to nearest 0.5, so total may differ slightly
      expect(Math.abs(total - presets[mode])).toBeLessThanOrEqual(1.5)
    })
  })
})
