/**
 * Test suite for Player Routes
 * Tests pagination, shuffling, and field mapping
 */ import { describe, it, expect, jest, beforeEach, afterEach } from "@jest/globals";
import request from "supertest";
import express from "express";
import { createPlayerRoutes } from "../playerRoutes.js";
import { AppState } from "../../../../utils/state.js";
// Mock the database module
jest.mock("../../../database.js");
describe("Player Routes", ()=>{
    let app;
    let mockDb;
    // Track the router across tests so afterAll can shut down the
    // BufferManager's setInterval. Without this Jest hangs forever
    // waiting for the timer handle to release — the GH Actions
    // 6-hour-job-timeout cost incident on 2026-05-26.
    let playerRouter = null;
    beforeEach(()=>{
        app = express();
        app.use(express.json());
        // Create mock database
        mockDb = {
            getAllTracks: jest.fn(),
            getRandomTracks: jest.fn(),
            getTrack: jest.fn(),
            getAlbums: jest.fn().mockReturnValue([]),
            db: {
                prepare: jest.fn().mockReturnValue({
                    all: jest.fn(),
                    get: jest.fn()
                })
            }
        };
        // Create mock database getter
        const mockDbGetter = async ()=>mockDb;
        // Create mock AppState
        const appState = new AppState();
        // Mount routes with injected database
        playerRouter = createPlayerRoutes(appState, mockDbGetter);
        app.use("/api", playerRouter);
    });
    afterEach(()=>{
        jest.clearAllMocks();
        jest.useRealTimers();
        // Each beforeEach makes a NEW router with its own BufferManager. Shut
        // the previous one down here so successive tests don't leave a chain
        // of orphaned setIntervals.
        playerRouter?.shutdown();
        playerRouter = null;
    });
    afterAll(()=>{
        jest.clearAllTimers();
        // Safety net — afterEach should already have run, but in case a test
        // bailed before mounting we don't want to leak.
        playerRouter?.shutdown();
    });
    describe("GET /api/player/tracks", ()=>{
        const mockTracks = [
            {
                path: "/music/track1.mp3",
                title: "Track 1",
                artist: "Artist 1",
                album: "Album 1",
                duration: 180,
                track_no: 1,
                artwork_hash: "abc123"
            },
            {
                path: "/music/track2.mp3",
                title: "Track 2",
                artist: "Artist 2",
                album: "Album 2",
                duration: 200,
                track_no: 2,
                artwork_hash: "def456"
            },
            {
                path: "/music/track3.mp3",
                title: "Track 3",
                artist: "Artist 3",
                album: "Album 3",
                duration: 240,
                track_no: 3,
                artwork_hash: null
            }
        ];
        it("should return paginated tracks with correct total", async ()=>{
            mockDb.getAllTracks.mockReturnValue({
                tracks: mockTracks.slice(0, 2),
                total: 3
            });
            const response = await request(app).get("/api/player/tracks").query({
                limit: 2,
                offset: 0
            });
            expect(response.status).toBe(200);
            expect(response.body).toMatchObject({
                total: 3,
                limit: 2,
                offset: 0,
                hasMore: true
            });
            expect(response.body.tracks).toHaveLength(2);
            expect(response.body.tracks[0]).toMatchObject({
                id: "/music/track1.mp3",
                title: "Track 1",
                artist: "Artist 1",
                album: "Album 1",
                duration: 180,
                trackNumber: 1,
                url: "/api/stream/%2Fmusic%2Ftrack1.mp3",
                artwork: "/artwork/abc123"
            });
            expect(mockDb.getAllTracks).toHaveBeenCalledWith(2, 0, "artist, album, track_no");
        });
        it("should handle offset correctly", async ()=>{
            mockDb.getAllTracks.mockReturnValue({
                tracks: [
                    mockTracks[2]
                ],
                total: 3
            });
            const response = await request(app).get("/api/player/tracks").query({
                limit: 1,
                offset: 2
            });
            expect(response.status).toBe(200);
            expect(response.body).toMatchObject({
                total: 3,
                limit: 1,
                offset: 2,
                hasMore: false
            });
            expect(response.body.tracks).toHaveLength(1);
            expect(response.body.tracks[0].title).toBe("Track 3");
            expect(response.body.tracks[0].artwork).toBeNull(); // No hash
            expect(mockDb.getAllTracks).toHaveBeenCalledWith(1, 2, "artist, album, track_no");
        });
        it("should handle shuffle mode", async ()=>{
            // Shuffle samples in SQL (getRandomTracks) — the library is never
            // materialized and getAllTracks is not involved.
            mockDb.getRandomTracks.mockReturnValue({
                tracks: mockTracks.slice(0, 2),
                total: 3
            });
            const response = await request(app).get("/api/player/tracks").query({
                limit: 2,
                offset: 0,
                shuffle: "true"
            });
            expect(response.status).toBe(200);
            expect(response.body).toMatchObject({
                total: 3,
                limit: 2,
                offset: 0
            });
            expect(response.body.tracks).toHaveLength(2);
            // Samples only the requested page size, in SQL
            expect(mockDb.getRandomTracks).toHaveBeenCalledWith(2);
            expect(mockDb.getAllTracks).not.toHaveBeenCalled();
            // Tracks should be from our mock data
            const returnedIds = response.body.tracks.map((t)=>t.id);
            returnedIds.forEach((id)=>{
                expect(mockTracks.some((t)=>t.path === id)).toBe(true);
            });
        });
        it("should enforce max limit of 500", async ()=>{
            mockDb.getAllTracks.mockReturnValue({
                tracks: [],
                total: 0
            });
            await request(app).get("/api/player/tracks").query({
                limit: 1000
            });
            // Should cap at 500
            expect(mockDb.getAllTracks).toHaveBeenCalledWith(500, 0, "artist, album, track_no");
        });
        it("should use defaults when no params provided", async ()=>{
            mockDb.getAllTracks.mockReturnValue({
                tracks: [],
                total: 0
            });
            const response = await request(app).get("/api/player/tracks");
            expect(response.status).toBe(200);
            expect(response.body.limit).toBe(100);
            expect(response.body.offset).toBe(0);
            expect(mockDb.getAllTracks).toHaveBeenCalledWith(100, 0, "artist, album, track_no");
        });
        it("should handle database errors gracefully", async ()=>{
            mockDb.getAllTracks.mockImplementation(()=>{
                throw new Error("Database error");
            });
            const response = await request(app).get("/api/player/tracks");
            expect(response.status).toBe(500);
            expect(response.body).toEqual({
                error: "Failed to get tracks"
            });
        });
    });
});

//# sourceMappingURL=playerRoutes.test.js.map