/**
 * @file TextToSpeechButton.test.tsx
 * @description Tests for the TextToSpeechButton "Read aloud" component
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { TextToSpeechButton } from "./TextToSpeechButton";

// Mock the useTextToSpeech hook
const mockSynthesize = vi.fn();
const mockPlay = vi.fn();
const mockPause = vi.fn();
const mockStop = vi.fn();

vi.mock("@/hooks/useTextToSpeech", () => ({
  useTextToSpeech: vi.fn(() => ({
    synthesize: mockSynthesize,
    play: mockPlay,
    pause: mockPause,
    stop: mockStop,
    audioUrl: null,
    isLoading: false,
    error: null,
    isPlaying: false,
    duration: 0,
    currentTime: 0,
  })),
}));

// Import after mocking
import { useTextToSpeech } from "@/hooks/useTextToSpeech";

const mockUseTextToSpeech = useTextToSpeech as ReturnType<typeof vi.fn>;

// Mock HTMLAudioElement for AudioPlayer used inside TextToSpeechButton
class MockAudio {
  src = "";
  currentTime = 0;
  duration = 60;
  paused = true;
  playbackRate = 1;
  volume = 1;
  onended: (() => void) | null = null;
  ontimeupdate: (() => void) | null = null;
  onloadedmetadata: (() => void) | null = null;
  onerror: ((e: unknown) => void) | null = null;

  play(): Promise<void> {
    this.paused = false;
    return Promise.resolve();
  }

  pause(): void {
    this.paused = true;
  }

  addEventListener(): void {
    // no-op
  }

  removeEventListener(): void {
    // no-op
  }
}

vi.stubGlobal("Audio", MockAudio);

describe("TextToSpeechButton", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockUseTextToSpeech.mockReturnValue({
      synthesize: mockSynthesize,
      play: mockPlay,
      pause: mockPause,
      stop: mockStop,
      audioUrl: null,
      isLoading: false,
      error: null,
      isPlaying: false,
      duration: 0,
      currentTime: 0,
    });
  });

  describe("rendering", () => {
    it("should render a read aloud button", () => {
      render(<TextToSpeechButton text="Hello world" />);

      const button = screen.getByRole("button", { name: /read aloud/i });
      expect(button).toBeInTheDocument();
    });

    it("should not render AudioPlayer initially when no audio is synthesized", () => {
      render(<TextToSpeechButton text="Hello world" />);

      expect(screen.queryByRole("region", { name: /audio player/i })).not.toBeInTheDocument();
    });
  });

  describe("click behavior", () => {
    it("should call synthesize with text on click", async () => {
      const user = userEvent.setup();
      mockSynthesize.mockResolvedValueOnce(undefined);

      render(<TextToSpeechButton text="Hello world" />);

      const button = screen.getByRole("button", { name: /read aloud/i });
      await user.click(button);

      expect(mockSynthesize).toHaveBeenCalledWith("Hello world", undefined);
    });

    it("should pass voice and tier options when provided", async () => {
      const user = userEvent.setup();
      mockSynthesize.mockResolvedValueOnce(undefined);

      render(<TextToSpeechButton text="Hello" voice="alloy" tier="premium" />);

      const button = screen.getByRole("button", { name: /read aloud/i });
      await user.click(button);

      expect(mockSynthesize).toHaveBeenCalledWith("Hello", {
        voice: "alloy",
        tier: "premium",
      });
    });
  });

  describe("loading state", () => {
    it("should show loading indicator while synthesizing", () => {
      mockUseTextToSpeech.mockReturnValue({
        synthesize: mockSynthesize,
        play: mockPlay,
        pause: mockPause,
        stop: mockStop,
        audioUrl: null,
        isLoading: true,
        error: null,
        isPlaying: false,
        duration: 0,
        currentTime: 0,
      });

      render(<TextToSpeechButton text="Hello world" />);

      const button = screen.getByRole("button", { name: /synthesizing/i });
      expect(button).toBeInTheDocument();
      expect(button).toBeDisabled();
    });
  });

  describe("audio player integration", () => {
    it("should show AudioPlayer when audio is available", () => {
      mockUseTextToSpeech.mockReturnValue({
        synthesize: mockSynthesize,
        play: mockPlay,
        pause: mockPause,
        stop: mockStop,
        audioUrl: "blob:mock-url",
        isLoading: false,
        error: null,
        isPlaying: false,
        duration: 30,
        currentTime: 0,
      });

      render(<TextToSpeechButton text="Hello world" />);

      expect(screen.getByRole("region", { name: /audio player/i })).toBeInTheDocument();
    });
  });

  describe("error state", () => {
    it("should display error message when synthesis fails", () => {
      mockUseTextToSpeech.mockReturnValue({
        synthesize: mockSynthesize,
        play: mockPlay,
        pause: mockPause,
        stop: mockStop,
        audioUrl: null,
        isLoading: false,
        error: "Synthesis failed",
        isPlaying: false,
        duration: 0,
        currentTime: 0,
      });

      render(<TextToSpeechButton text="Hello world" />);

      expect(screen.getByText(/synthesis failed/i)).toBeInTheDocument();
    });
  });

  describe("accessibility", () => {
    it("should have proper aria label on button", () => {
      render(<TextToSpeechButton text="Hello world" />);

      const button = screen.getByRole("button", { name: /read aloud/i });
      expect(button).toBeInTheDocument();
    });
  });

  describe("design", () => {
    it("should not use aggressive colors", () => {
      const { container } = render(<TextToSpeechButton text="Hello world" />);

      const allElements = container.querySelectorAll("*");
      allElements.forEach((el) => {
        const className = el.className;
        if (typeof className === "string") {
          expect(className).not.toMatch(/bg-red-|text-red-|border-red-/);
        }
      });
    });
  });
});
