import { describe, it, expect } from 'vitest';
import { gainedIn, timeToGain, gaugeAfter, elapsed, computeGaugeState, TimerState } from '@domain/services/GaugeCalculator';
import type { GaugeRecharge } from '@domain/services/GaugeCalculator';

describe('GaugeCalculator', () => {

  describe('gainedIn', () => {
    it('tier 1 only: 100 pts in 100 sec', () => {
      // Level 100, 100 sec = 10 ticks, rate 10 → gain 100
      expect(gainedIn(100, 100)).toBe(100);
    });

    it('zero seconds = zero gain', () => {
      expect(gainedIn(50000, 0)).toBe(0);
    });

    it('level 0 = zero gain', () => {
      expect(gainedIn(0, 100)).toBe(0);
    });

    it('crosses tier boundary 2→1', () => {
      // Level 40010, 10 sec = 1 tick. In tier 2 (40001-70000), rate 20.
      // But only 10 pts above 40000, so 10/20 = 0 full ticks at rate 20 → 0 gained from tier 2
      // Actually: a = 40010-40000 = 10, m = floor(10/20) = 0, u = 0. Move to tier 1.
      // Tier 1: g=40010 > 0, a=40010-0=40010... wait g is now 40010 still since u=0
      // Let me recalculate: g=40010, tier {lo:40000, r:20}: a=10, m=0, u=0. No drain.
      // Tier {lo:0, r:10}: g=40010 > 0, a=40010, m=4001, u=min(4001,1)=1, out=10
      // Actually g should still be 40010 since we didn't drain. Hmm but the loop says if g<=lo continue
      // g=40010 > 40000 → process. a=10, m=floor(10/20)=0, u=0.
      // g=40010 > 0 → process. a=40010, m=floor(40010/10)=4001, u=min(4001,1)=1, out=10.
      expect(gainedIn(40010, 10)).toBe(10);
    });

    it('full tier 4 drain', () => {
      // Level 100000, 250 ticks needed to drain tier 4 (10000/40=250)
      // 250 ticks = 2500 sec
      expect(gainedIn(100000, 2500)).toBe(10000);
    });

    it('large gauge drains through multiple tiers', () => {
      // Level 100000, 5000 sec = 500 ticks
      // Tier 4: 100000→90000 = 10000/40 = 250 ticks → gain 10000, tl=250
      // Tier 3: 90000→70000 = 20000/30 = 666 ticks, but only 250 left → 250*30 = 7500
      // Total = 17500
      expect(gainedIn(100000, 5000)).toBe(17500);
    });

    it('negative level clamped to 0', () => {
      expect(gainedIn(-100, 100)).toBe(0);
    });

    it('level above 100000 clamped', () => {
      expect(gainedIn(150000, 10)).toBe(40); // tier 4 rate
    });
  });

  describe('timeToGain', () => {
    it('zero points = zero time', () => {
      expect(timeToGain(50000, 0)).toBe(0);
    });

    it('negative points = zero time', () => {
      expect(timeToGain(50000, -10)).toBe(0);
    });

    it('tier 1: 100 pts needs 100 sec', () => {
      // Level 100, need 100 pts at rate 10 → 10 ticks → 100 sec
      expect(timeToGain(100, 100)).toBe(100);
    });

    it('tier 2: 1000 pts from level 50000', () => {
      // Level 50000, tier 2 rate 20. 1000/20 = 50 ticks → 500 sec
      expect(timeToGain(50000, 1000)).toBe(500);
    });

    it('returns Infinity if level is 0', () => {
      expect(timeToGain(0, 100)).toBe(Infinity);
    });
  });

  describe('gaugeAfter', () => {
    it('no time = same level', () => {
      expect(gaugeAfter(50000, 0)).toBe(50000);
    });

    it('tier 1 full drain', () => {
      // Level 100, 100 sec = 10 ticks. 100/10=10 ticks needed. Exactly drains to 0.
      expect(gaugeAfter(100, 100)).toBe(0);
    });

    it('does not go below 0', () => {
      expect(gaugeAfter(50, 1000)).toBe(0);
    });

    it('partial drain in tier 2', () => {
      // Level 50000, 10 sec = 1 tick. Rate 20 at tier 2. 50000-20 = 49980
      expect(gaugeAfter(50000, 10)).toBe(49980);
    });
  });

  describe('computeGaugeState', () => {
    const NO_RECHARGES: GaugeRecharge[] = [];

    it('sans recharge ni cap : identique à gainedIn', () => {
      const el = 1000;
      const startGl = 50000;
      const { gained, curGl, effectiveEl } = computeGaugeState(startGl, NO_RECHARGES, Infinity, el);
      expect(gained).toBe(gainedIn(startGl, el));
      expect(curGl).toBe(gaugeAfter(startGl, el));
      expect(effectiveEl).toBe(el);
    });

    it('cap à 0 dès le départ (stat déjà au max) → gel immédiat', () => {
      const { gained, effectiveEl } = computeGaugeState(90000, NO_RECHARGES, 0, 5000);
      expect(gained).toBe(0);
      expect(effectiveEl).toBe(0);
    });

    it('cap atteint en cours de segment → gel précis', () => {
      // Tier 2 (50000), rate 20/tick. On veut 200 pts → 10 ticks → 100 sec.
      const { gained, effectiveEl } = computeGaugeState(50000, NO_RECHARGES, 200, 5000);
      expect(gained).toBe(200);
      expect(effectiveEl).toBe(100); // timeToGain(50000, 200) = 100 sec
    });

    it('une recharge avant la fin, pas de cap → somme des deux segments', () => {
      // Seg1 : 50000, 100 sec → gainedIn(50000, 100) = 10 ticks × 20 = 200 pts
      // Recharge à 80000 à t=100
      // Seg2 : 80000, 100 sec → gainedIn(80000, 100) = 10 ticks × 30 = 300 pts
      // Total = 500 pts
      const recharges: GaugeRecharge[] = [{ atSec: 100, level: 80000 }];
      const { gained, effectiveEl } = computeGaugeState(50000, recharges, Infinity, 200);
      expect(gained).toBe(500);
      expect(effectiveEl).toBe(200);
    });

    it('cap atteint dans le premier segment (avant la recharge)', () => {
      // Seg1 : 50000, cap=100 pts → gel à timeToGain(50000,100)=50 sec
      // Recharge à t=100 ne doit pas être prise en compte
      const recharges: GaugeRecharge[] = [{ atSec: 100, level: 90000 }];
      const { gained, effectiveEl } = computeGaugeState(50000, recharges, 100, 500);
      expect(gained).toBe(100);
      expect(effectiveEl).toBe(50); // gel avant la recharge
    });

    it('cap atteint dans le deuxième segment (après une recharge)', () => {
      // Seg1 : 50000, 100 sec → 200 pts, cap=500 → pas encore atteint
      // Recharge à 80000 à t=100
      // Seg2 : 80000, cap restant=300 pts → timeToGain(80000,300)=100 sec → effectiveEl=100+100=200
      const recharges: GaugeRecharge[] = [{ atSec: 100, level: 80000 }];
      const { gained, effectiveEl } = computeGaugeState(50000, recharges, 500, 9999);
      expect(gained).toBe(500);
      expect(effectiveEl).toBe(200);
    });

    it('plusieurs recharges successives', () => {
      // Seg1 : 10000 (tier1), 50 sec → 5 ticks × 10 = 50 pts
      // Recharge à 50000 à t=50
      // Seg2 : 50000 (tier2), 50 sec → 5 ticks × 20 = 100 pts
      // Recharge à 90000 à t=100
      // Seg3 : 90000 (tier3), 50 sec → 5 ticks × 30 = 150 pts
      // Total = 300 pts
      const recharges: GaugeRecharge[] = [
        { atSec: 50, level: 50000 },
        { atSec: 100, level: 90000 },
      ];
      const { gained } = computeGaugeState(10000, recharges, Infinity, 150);
      expect(gained).toBe(300);
    });

    it('recharge après le cap → le cap prime, recharge ignorée', () => {
      // Cap à 50 pts → gel à 50 sec (tier2, rate20, 50/20=2.5→3 ticks=30sec... wait)
      // timeToGain(50000, 50) = ceil(50/20)*10 = 3*10 = 30 sec
      // Recharge à t=100 → ignorée car gel à t=30
      const recharges: GaugeRecharge[] = [{ atSec: 100, level: 90000 }];
      const { gained, effectiveEl } = computeGaugeState(50000, recharges, 50, 9999);
      expect(gained).toBe(50);
      expect(effectiveEl).toBe(30);
    });

    it('curGl reflète le niveau après le gel', () => {
      // Tier2 (50000), cap=200 pts → 10 ticks → 100 sec, gauge = 50000-10*20=49800
      const { curGl } = computeGaugeState(50000, NO_RECHARGES, 200, 9999);
      expect(curGl).toBe(49800);
    });

    it('curGl reflète la recharge dans le deuxième segment', () => {
      // Seg1: 50000, 100sec → curGl après seg1 = gaugeAfter(50000,100)=49800 mais recharge à 80000
      // Seg2: 80000, no cap → curGl = gaugeAfter(80000, 100sec) = 80000-10*30=79700
      const recharges: GaugeRecharge[] = [{ atSec: 100, level: 80000 }];
      const { curGl } = computeGaugeState(50000, recharges, Infinity, 200);
      expect(curGl).toBe(gaugeAfter(80000, 100));
    });
  });

  describe('elapsed', () => {
    it('no start time = 0', () => {
      const t: TimerState = { startTime: null, running: false, pausedAt: null, pausedMs: 0 };
      expect(elapsed(t)).toBe(0);
    });

    it('running timer', () => {
      const now = Date.now();
      const t: TimerState = { startTime: now - 10000, running: true, pausedAt: null, pausedMs: 0 };
      const el = elapsed(t);
      expect(el).toBeGreaterThanOrEqual(9.9);
      expect(el).toBeLessThanOrEqual(10.5);
    });

    it('paused timer', () => {
      const now = Date.now();
      const t: TimerState = { startTime: now - 10000, running: false, pausedAt: now - 5000, pausedMs: 0 };
      const el = elapsed(t);
      expect(el).toBeCloseTo(5, 0);
    });

    it('paused timer with accumulated pause', () => {
      const now = Date.now();
      const t: TimerState = { startTime: now - 20000, running: false, pausedAt: now - 5000, pausedMs: 5000 };
      // elapsed = (pausedAt - startTime - pausedMs) / 1000 = (15000 - 5000) / 1000 = 10
      expect(elapsed(t)).toBeCloseTo(10, 0);
    });
  });
});
