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

// Mock the useVoiceInput hook
const mockStartRecording = vi.fn();
const mockStopRecording = vi.fn();

vi.mock("@/hooks/useVoiceInput", () => ({
  useVoiceInput: vi.fn(() => ({
    isRecording: false,
    startRecording: mockStartRecording,
    stopRecording: mockStopRecording,
    transcript: "",
    partialTranscript: "",
    error: null,
    audioLevel: 0,
  })),
}));

// We need to import after mocking
import { useVoiceInput } from "@/hooks/useVoiceInput";

describe("VoiceInput", (): void => {
  beforeEach((): void => {
    vi.clearAllMocks();
    // Reset mock implementation to default
    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: false,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "",
      partialTranscript: "",
      error: null,
      audioLevel: 0,
    });
  });

  it("should render a microphone button", (): void => {
    render(<VoiceInput />);

    const button = screen.getByRole("button", {
      name: /start voice input/i,
    });
    expect(button).toBeInTheDocument();
  });

  it("should have accessible aria label", (): void => {
    render(<VoiceInput />);

    const button = screen.getByRole("button", {
      name: /start voice input/i,
    });
    expect(button).toHaveAttribute("aria-label", "Start voice input");
  });

  it("should call startRecording when mic button is clicked", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<VoiceInput />);

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

    expect(mockStartRecording).toHaveBeenCalledTimes(1);
  });

  it("should show recording state when isRecording is true", (): void => {
    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: true,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "",
      partialTranscript: "",
      error: null,
      audioLevel: 0.5,
    });

    render(<VoiceInput />);

    const button = screen.getByRole("button", {
      name: /stop voice input/i,
    });
    expect(button).toBeInTheDocument();
  });

  it("should call stopRecording when mic button is clicked while recording", async (): Promise<void> => {
    const user = userEvent.setup();

    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: true,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "",
      partialTranscript: "",
      error: null,
      audioLevel: 0.5,
    });

    render(<VoiceInput />);

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

    expect(mockStopRecording).toHaveBeenCalledTimes(1);
  });

  it("should display partial transcription text", (): void => {
    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: true,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "",
      partialTranscript: "hello worl",
      error: null,
      audioLevel: 0.3,
    });

    render(<VoiceInput />);

    expect(screen.getByText("hello worl")).toBeInTheDocument();
  });

  it("should display final transcript text", (): void => {
    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: false,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "hello world",
      partialTranscript: "",
      error: null,
      audioLevel: 0,
    });

    render(<VoiceInput />);

    expect(screen.getByText("hello world")).toBeInTheDocument();
  });

  it("should display error message", (): void => {
    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: false,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "",
      partialTranscript: "",
      error: "Microphone access not available",
      audioLevel: 0,
    });

    render(<VoiceInput />);

    expect(screen.getByText("Microphone access not available")).toBeInTheDocument();
  });

  it("should call onTranscript callback prop", (): void => {
    const onTranscript = vi.fn();

    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: false,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "final text",
      partialTranscript: "",
      error: null,
      audioLevel: 0,
    });

    render(<VoiceInput onTranscript={onTranscript} />);

    // The onTranscript prop is passed to the hook - we verify the prop is accepted
    expect(useVoiceInput).toHaveBeenCalledWith(
      expect.objectContaining({
        onTranscript,
      })
    );
  });

  it("should use calm, non-aggressive design for recording indicator", (): void => {
    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: true,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "",
      partialTranscript: "",
      error: null,
      audioLevel: 0.5,
    });

    render(<VoiceInput />);

    // Check there are no aggressive red colors in the recording state
    const button = screen.getByRole("button", { name: /stop voice input/i });
    const className = button.className;
    expect(className).not.toMatch(/bg-red-|text-red-|border-red-/);
  });

  it("should use calm design for error display", (): void => {
    vi.mocked(useVoiceInput).mockReturnValue({
      isRecording: false,
      startRecording: mockStartRecording,
      stopRecording: mockStopRecording,
      transcript: "",
      partialTranscript: "",
      error: "Something went wrong",
      audioLevel: 0,
    });

    render(<VoiceInput />);

    const errorEl = screen.getByText("Something went wrong");
    const className = errorEl.className;
    expect(className).not.toMatch(/text-red-600|bg-red-/);
  });

  it("should be disabled when disabled prop is true", (): void => {
    render(<VoiceInput disabled />);

    const button = screen.getByRole("button", {
      name: /start voice input/i,
    });
    expect(button).toBeDisabled();
  });
});
