/*
 * Copyright 2025 New Vector Ltd.
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
 * Please see LICENSE files in the repository root for full details.
 */

import { type ChangeEvent, type KeyboardEvent as ReactKeyboardEvent } from "react";
import { waitFor } from "@testing-library/dom";

import { type Playback, PlaybackState } from "../../../src/audio/Playback";
import { AudioPlayerViewModel } from "../../../src/viewmodels/audio/AudioPlayerViewModel";
import { MockedPlayback } from "../../unit-tests/audio/MockedPlayback";

describe("AudioPlayerViewModel", () => {
    let playback: Playback;
    beforeEach(() => {
        playback = new MockedPlayback(PlaybackState.Decoding, 50, 10) as unknown as Playback;
    });

    it("should return the snapshot", () => {
        const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
        expect(vm.getSnapshot()).toMatchObject({
            mediaName: "mediaName",
            sizeBytes: 8000,
            playbackState: "decoding",
            durationSeconds: 50,
            playedSeconds: 10,
            percentComplete: 20,
            error: false,
        });
    });

    it("should toggle the playback state", async () => {
        const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });

        await vm.togglePlay();
        expect(playback.toggle).toHaveBeenCalled();
    });

    it("should move the playback on seekbar change", async () => {
        const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
        await vm.onSeekbarChange({ target: { value: "20" } } as ChangeEvent<HTMLInputElement>);
        expect(playback.skipTo).toHaveBeenCalledWith(10); // 20% of 50 seconds
    });

    it("should has error=true when playback.prepare fails", async () => {
        jest.spyOn(playback, "prepare").mockRejectedValue(new Error("Failed to prepare playback"));
        const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
        await waitFor(() => expect(vm.getSnapshot().error).toBe(true));
    });

    it("should handle key down events", () => {
        const vm = new AudioPlayerViewModel({ playback, mediaName: "mediaName" });
        let event = new KeyboardEvent("keydown", { key: " " }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
        vm.onKeyDown(event);
        expect(playback.toggle).toHaveBeenCalled();

        event = new KeyboardEvent("keydown", { key: "ArrowLeft" }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
        vm.onKeyDown(event);
        expect(playback.skipTo).toHaveBeenCalledWith(10 - 5); // 5 seconds back

        event = new KeyboardEvent("keydown", { key: "ArrowRight" }) as unknown as ReactKeyboardEvent<HTMLDivElement>;
        vm.onKeyDown(event);
        expect(playback.skipTo).toHaveBeenCalledWith(10 + 5); // 5 seconds forward
    });

    it("should update snapshot when setProps is called with new mediaName", () => {
        const vm = new AudioPlayerViewModel({ playback, mediaName: "oldName" });
        expect(vm.getSnapshot().mediaName).toBe("oldName");

        vm.setProps({ mediaName: "newName" });
        expect(vm.getSnapshot().mediaName).toBe("newName");
    });
});
