import { fireEvent, render, screen, within } from "@testing-library/react";
import { afterEach, beforeEach, expect, test, vi } from "vitest";

import { AdminAudioPlayer } from "@/shared/ui/admin-audio-player";
import { AudioPlaybackProvider } from "@/shared/ui/audio-playback-provider";

let playSpy: ReturnType<typeof vi.spyOn>;
let pauseSpy: ReturnType<typeof vi.spyOn>;
let loadSpy: ReturnType<typeof vi.spyOn>;

beforeEach(() => {
  playSpy = vi.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue(undefined);
  pauseSpy = vi.spyOn(HTMLMediaElement.prototype, "pause").mockImplementation(() => undefined);
  loadSpy = vi.spyOn(HTMLMediaElement.prototype, "load").mockImplementation(() => undefined);
});

afterEach(() => {
  playSpy.mockRestore();
  pauseSpy.mockRestore();
  loadSpy.mockRestore();
});

test("AdminAudioPlayer wraps native audio with controls and no download or autoplay", () => {
  render(<AdminAudioPlayer playerId="one" src="https://cdn.example.com/signed/audio.m4a?token=secret" title="샘플 오디오" />);

  const audio = document.querySelector("audio");
  expect(audio).toHaveAttribute("src", "https://cdn.example.com/signed/audio.m4a?token=secret");
  expect(audio).not.toHaveAttribute("autoplay");
  expect(audio).toHaveAttribute("controlsList", "nodownload");
  expect(screen.getByRole("button", { name: "재생" })).toBeInTheDocument();
  expect(screen.getByRole("slider", { name: "재생 위치" })).toBeInTheDocument();
  expect(screen.getByRole("slider", { name: "볼륨" })).toHaveValue("1");
  expect(screen.getByRole("combobox", { name: "재생 속도" })).toBeInTheDocument();
});

test("AdminAudioPlayer matches the compact reference control anatomy", () => {
  render(<AdminAudioPlayer playerId="one" src="https://cdn.example.com/signed/audio.m4a?token=secret" title="샘플 오디오" />);

  const player = screen.getByRole("group", { name: "샘플 오디오 오디오 플레이어" });
  const controls = within(player).getByRole("group", { name: "재생 제어" });
  expect(within(controls).getByRole("button", { name: "재생" })).toBeInTheDocument();
  const seek = within(controls).getByRole("slider", { name: "재생 위치" });
  const speed = within(controls).getByRole("combobox", { name: "재생 속도" });
  const volume = within(controls).getByRole("slider", { name: "볼륨" });
  expect.soft(player).toHaveClass("px-1", "sm:px-3");
  expect(seek).toHaveClass("min-w-11");
  expect.soft(speed).toHaveClass("w-11");
  expect(speed).toHaveClass("text-base");
  expect.soft(volume).toHaveClass("basis-11");
  expect(volume).toHaveClass("min-w-11");
  expect(volume).toBeInTheDocument();
  expect(volume.className).not.toContain("hidden");
  expect.soft(within(controls).queryByText("0:00/0:00")).toBeInTheDocument();
  expect(within(player).queryByText("볼륨")).not.toBeInTheDocument();
  expect(within(player).queryByText("재생 속도")).not.toBeInTheDocument();
  expect(player.querySelector("img")).not.toBeInTheDocument();
  expect(player.querySelector("video")).not.toBeInTheDocument();
});

test("AudioPlaybackProvider keeps only one player active by player id", () => {
  render(
    <AudioPlaybackProvider>
      <AdminAudioPlayer playerId="one" src="https://cdn.example.com/signed/one.m4a?token=secret-one" title="첫 번째" />
      <AdminAudioPlayer playerId="two" src="https://cdn.example.com/signed/two.m4a?token=secret-two" title="두 번째" />
    </AudioPlaybackProvider>,
  );

  const playButtons = screen.getAllByRole("button", { name: "재생" });
  const firstPlayButton = playButtons[0];
  const secondPlayButton = playButtons[1];
  if (firstPlayButton === undefined || secondPlayButton === undefined) {
    throw new Error("expected two play buttons");
  }

  fireEvent.click(firstPlayButton);
  fireEvent.click(secondPlayButton);

  expect(playSpy).toHaveBeenCalledTimes(2);
  expect(pauseSpy).toHaveBeenCalled();
});

test("AdminAudioPlayer supports keyboard play, seek, volume, speed, generic error, and manual retry only", () => {
  render(<AdminAudioPlayer playerId="one" src="https://cdn.example.com/signed/audio.m4a?token=secret" title="샘플 오디오" />);

  const player = screen.getByRole("group", { name: "샘플 오디오 오디오 플레이어" });
  fireEvent.keyDown(player, { key: " " });
  fireEvent.change(screen.getByRole("slider", { name: "재생 위치" }), { target: { value: "12" } });
  fireEvent.change(screen.getByRole("slider", { name: "볼륨" }), { target: { value: "0.5" } });
  fireEvent.change(screen.getByRole("combobox", { name: "재생 속도" }), { target: { value: "1.5" } });
  const audio = document.querySelector("audio");
  if (!(audio instanceof HTMLAudioElement)) {
    throw new Error("expected native audio element");
  }
  fireEvent.error(audio);

  expect(playSpy).toHaveBeenCalledTimes(1);
  expect(screen.getByRole("alert")).toHaveTextContent("오디오를 재생할 수 없습니다");
  fireEvent.click(screen.getByRole("button", { name: "오디오 다시 시도" }));
  expect(loadSpy).toHaveBeenCalledTimes(1);
  expect(playSpy).toHaveBeenCalledTimes(1);
});

test("AdminAudioPlayer ignores Enter and Space from descendant controls", () => {
  render(<AdminAudioPlayer playerId="one" src="https://cdn.example.com/signed/audio.m4a?token=secret" title="샘플 오디오" />);

  fireEvent.keyDown(screen.getByRole("combobox", { name: "재생 속도" }), { key: "Enter" });
  fireEvent.keyDown(screen.getByRole("button", { name: "재생" }), { key: " " });

  expect(playSpy).not.toHaveBeenCalled();
});

test("AudioPlaybackProvider and AdminAudioPlayer do not log or persist signed URLs", () => {
  const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
  const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined);
  const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
  const localStorageSpy = vi.spyOn(Storage.prototype, "setItem");
  const signedUrl = "https://cdn.example.com/signed/audio.m4a?token=secret";

  render(
    <AudioPlaybackProvider>
      <AdminAudioPlayer playerId="one" src={signedUrl} title="샘플 오디오" />
    </AudioPlaybackProvider>,
  );

  fireEvent.click(screen.getByRole("button", { name: "재생" }));
  const audio = document.querySelector("audio");
  if (!(audio instanceof HTMLAudioElement)) {
    throw new Error("expected native audio element");
  }
  fireEvent.error(audio);

  expect(logSpy).not.toHaveBeenCalled();
  expect(warnSpy).not.toHaveBeenCalled();
  expect(errorSpy).not.toHaveBeenCalled();
  expect(localStorageSpy).not.toHaveBeenCalledWith(expect.any(String), expect.stringContaining(signedUrl));

  logSpy.mockRestore();
  warnSpy.mockRestore();
  errorSpy.mockRestore();
  localStorageSpy.mockRestore();
});
