local createLibTest = require('/apis/libtest');
local createMusic = require('/apis/libmusic');

local testlib = createLibTest({ ... });

local function fakeFs(dir, entries)
  return {
    isDir = function(path) return path == dir; end,
    list = function(path)
      if path ~= dir then error('unexpected list: ' .. tostring(path)); end
      return entries;
    end,
  };
end

testlib.test('listTracks filters dfpwm, strips extension and sorts', function()
  local music = createMusic({
    dir = '/data/musics',
    fs = fakeFs('/data/musics', { 'Zebra.dfpwm', 'notes.txt', 'Apple.dfpwm' }),
  });
  local tracks = music.listTracks();
  testlib.assertEquals(#tracks, 2);
  testlib.assertEquals(tracks[1].name, 'Apple');
  testlib.assertEquals(tracks[1].path, '/data/musics/Apple.dfpwm');
  testlib.assertEquals(tracks[2].name, 'Zebra');
  testlib.assertEquals(tracks[2].path, '/data/musics/Zebra.dfpwm');
end);

testlib.test('listTracks returns empty when directory is missing', function()
  local music = createMusic({
    dir = '/data/musics',
    fs = { isDir = function() return false; end },
  });
  testlib.assertEquals(#music.listTracks(), 0);
end);

-- A speaker whose queue accepts `capacity` buffers before refusing. Tests
-- simulate a drained queue by resetting `queued` before firing the captured
-- `speaker_audio_empty` handler.
local function fakeSpeaker(capacity)
  local s = { played = {}, stops = 0, queued = 0, capacity = capacity or 1 };
  s.playAudio = function(buffer)
    if s.queued >= s.capacity then
      return false;
    end
    s.queued = s.queued + 1;
    s.played[#s.played + 1] = buffer;
    return true;
  end;
  s.stop = function()
    s.stops = s.stops + 1;
  end;
  return s;
end

local function fakeHandle(chunks)
  local index = 0;
  return {
    closed = false,
    read = function(_, _)
      index = index + 1;
      return chunks[index];
    end,
    close = function(self)
      self.closed = true;
    end,
  };
end

-- identity decoder: the played buffers equal the raw chunks for easy asserts
local fakeDfpwm = { make_decoder = function() return function(chunk) return chunk; end; end };

local function makePlayer(opts)
  local captured = {};
  local eventloop = {
    register = function(event, handler)
      captured[event] = handler;
      return function() end;
    end,
  };
  local music = createMusic({
    peripheral = { find = function(name) return name == 'speaker' and opts.speaker or nil; end },
    io = { open = function(path) return opts.handles[path]; end },
    dfpwm = fakeDfpwm,
  });
  local player = music.createPlayer(eventloop);
  return player, captured, eventloop;
end

testlib.test('play opens the file, primes the speaker and reports playing', function()
  local speaker = fakeSpeaker(1);
  local handle = fakeHandle({ 'a', 'b' });
  local player = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } });

  player.play('/song.dfpwm');

  testlib.assertEquals(player.getStatus(), 'playing');
  testlib.assertEquals(player.getCurrentPath(), '/song.dfpwm');
  testlib.assertEquals(#speaker.played, 1);
  testlib.assertEquals(speaker.played[1], 'a');
end);

testlib.test('back-pressure resumes feeding on speaker_audio_empty', function()
  local speaker = fakeSpeaker(1);
  local handle = fakeHandle({ 'a', 'b' });
  local player, captured = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } });

  player.play('/song.dfpwm'); -- 'a' queued, 'b' refused (pending)
  testlib.assertEquals(#speaker.played, 1);

  speaker.queued = 0; -- simulate the queue draining
  captured.speaker_audio_empty();

  testlib.assertEquals(speaker.played[2], 'b'); -- pending buffer replayed
  testlib.assertEquals(player.getStatus(), 'stopped'); -- then EOF
end);

testlib.test('pause stops feeding and resume loses no buffered chunk', function()
  local speaker = fakeSpeaker(1);
  local handle = fakeHandle({ 'a', 'b', 'c' });
  local player, captured = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } });

  player.play('/song.dfpwm'); -- 'a' queued, 'b' pending
  player.pause();
  testlib.assertEquals(player.getStatus(), 'paused');

  speaker.queued = 0;
  captured.speaker_audio_empty(); -- no-op while paused
  testlib.assertEquals(#speaker.played, 1);

  player.resume();
  testlib.assertEquals(player.getStatus(), 'playing');
  testlib.assertEquals(speaker.played[2], 'b'); -- pending 'b' fed, nothing dropped
end);

testlib.test('stop clears the speaker, closes the handle and resets state', function()
  local speaker = fakeSpeaker(1);
  local handle = fakeHandle({ 'a', 'b' });
  local player = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } });

  player.play('/song.dfpwm');
  player.stop();

  testlib.assertEquals(speaker.stops, 1);
  testlib.assertTrue(handle.closed, 'handle should be closed');
  testlib.assertEquals(player.getStatus(), 'stopped');
  testlib.assertEquals(player.getCurrentPath(), nil);
end);

testlib.test('reaching the end fires onFinish once and stays stopped', function()
  local speaker = fakeSpeaker(10); -- large queue: whole track flows in one pump
  local handle = fakeHandle({ 'a', 'b' });
  local player = makePlayer({ speaker = speaker, handles = { ['/song.dfpwm'] = handle } });

  local finishes = 0;
  player.onFinish(function() finishes = finishes + 1; end);

  player.play('/song.dfpwm');

  testlib.assertEquals(finishes, 1);
  testlib.assertEquals(player.getStatus(), 'stopped');
  testlib.assertEquals(#speaker.played, 2);
end);

testlib.run();
