/**
 * @file SpeechSettings.test.tsx
 * @description Tests for the SpeechSettings component
 *
 * Validates all settings sections: STT, TTS, Voice Preview, Provider Status.
 * Follows TDD: tests written before implementation.
 *
 * Issue #404
 */

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

// Mock the speech API
const mockGetVoices = vi.fn();
const mockGetHealthStatus = vi.fn();
const mockSynthesizeSpeech = vi.fn();

vi.mock("@/lib/api/speech", () => ({
  getVoices: (...args: unknown[]): unknown => mockGetVoices(...args) as unknown,
  getHealthStatus: (...args: unknown[]): unknown => mockGetHealthStatus(...args) as unknown,
  synthesizeSpeech: (...args: unknown[]): unknown => mockSynthesizeSpeech(...args) as unknown,
}));

// Mock the useTextToSpeech hook for voice preview
const mockSynthesize = vi.fn();

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

// Mock HTMLAudioElement for AudioPlayer used inside preview
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);

// Default mock responses
const mockVoicesResponse = {
  data: [
    { id: "voice-1", name: "Alloy", language: "en", tier: "default", isDefault: true },
    { id: "voice-2", name: "Nova", language: "en", tier: "default", isDefault: false },
    { id: "voice-3", name: "Premium Voice", language: "en", tier: "premium", isDefault: true },
  ],
};

const mockHealthResponse = {
  data: {
    stt: { available: true },
    tts: { available: true },
  },
};

describe("SpeechSettings", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockGetVoices.mockResolvedValue(mockVoicesResponse);
    mockGetHealthStatus.mockResolvedValue(mockHealthResponse);
    mockSynthesizeSpeech.mockResolvedValue(new Blob());
  });

  describe("rendering", () => {
    it("should render the speech settings heading", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Speech Settings")).toBeInTheDocument();
      });
    });

    it("should render the STT settings section", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Speech-to-Text")).toBeInTheDocument();
      });
    });

    it("should render the TTS settings section", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Text-to-Speech")).toBeInTheDocument();
      });
    });

    it("should render the provider status section", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Provider Status")).toBeInTheDocument();
      });
    });

    it("should render all four section cards", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Speech-to-Text")).toBeInTheDocument();
        expect(screen.getByText("Text-to-Speech")).toBeInTheDocument();
        expect(screen.getByText("Voice Preview")).toBeInTheDocument();
        expect(screen.getByText("Provider Status")).toBeInTheDocument();
      });
    });
  });

  describe("STT settings", () => {
    it("should render an enable/disable toggle for STT", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        const sttToggle = screen.getByRole("switch", { name: /enable speech-to-text/i });
        expect(sttToggle).toBeInTheDocument();
      });
    });

    it("should render a language preference dropdown", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Language")).toBeInTheDocument();
      });
    });

    it("should toggle STT enabled state when clicked", async () => {
      const user = userEvent.setup();
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByRole("switch", { name: /enable speech-to-text/i })).toBeInTheDocument();
      });

      const sttToggle = screen.getByRole("switch", { name: /enable speech-to-text/i });
      // Default should be checked (enabled)
      expect(sttToggle).toBeChecked();

      await user.click(sttToggle);
      expect(sttToggle).not.toBeChecked();
    });
  });

  describe("TTS settings", () => {
    it("should render an enable/disable toggle for TTS", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        const ttsToggle = screen.getByRole("switch", { name: /enable text-to-speech/i });
        expect(ttsToggle).toBeInTheDocument();
      });
    });

    it("should render a voice selector", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Default Voice")).toBeInTheDocument();
      });
    });

    it("should render a tier preference selector", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Provider Tier")).toBeInTheDocument();
      });
    });

    it("should render an auto-play toggle", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        const autoPlayToggle = screen.getByRole("switch", { name: /auto-play/i });
        expect(autoPlayToggle).toBeInTheDocument();
      });
    });

    it("should render a speed control slider", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Speed")).toBeInTheDocument();
        const slider = screen.getByRole("slider");
        expect(slider).toBeInTheDocument();
      });
    });

    it("should display the current speed value", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        // The speed display label shows "1.0x" next to the Speed label
        const speedLabels = screen.getAllByText("1.0x");
        expect(speedLabels.length).toBeGreaterThanOrEqual(1);
      });
    });

    it("should toggle TTS enabled state when clicked", async () => {
      const user = userEvent.setup();
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByRole("switch", { name: /enable text-to-speech/i })).toBeInTheDocument();
      });

      const ttsToggle = screen.getByRole("switch", { name: /enable text-to-speech/i });
      expect(ttsToggle).toBeChecked();

      await user.click(ttsToggle);
      expect(ttsToggle).not.toBeChecked();
    });

    it("should toggle auto-play state when clicked", async () => {
      const user = userEvent.setup();
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByRole("switch", { name: /auto-play/i })).toBeInTheDocument();
      });

      const autoPlayToggle = screen.getByRole("switch", { name: /auto-play/i });
      // Default should be unchecked
      expect(autoPlayToggle).not.toBeChecked();

      await user.click(autoPlayToggle);
      expect(autoPlayToggle).toBeChecked();
    });
  });

  describe("voice selector", () => {
    it("should fetch voices on mount", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(mockGetVoices).toHaveBeenCalled();
      });
    });

    it("should display voice options after fetching", async () => {
      const user = userEvent.setup();
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(mockGetVoices).toHaveBeenCalled();
      });

      // Open the voice selector by clicking the trigger button (id="tts-voice")
      const voiceButton = document.getElementById("tts-voice");
      expect(voiceButton).toBeTruthy();
      if (!voiceButton) throw new Error("Voice button not found");
      await user.click(voiceButton);

      await waitFor(() => {
        expect(screen.getByText("Alloy")).toBeInTheDocument();
        expect(screen.getByText("Nova")).toBeInTheDocument();
      });
    });

    it("should handle API error gracefully when fetching voices", async () => {
      mockGetVoices.mockRejectedValueOnce(new Error("Network error"));

      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText(/unable to load voices/i)).toBeInTheDocument();
      });
    });
  });

  describe("voice preview", () => {
    it("should render a voice preview section", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Voice Preview")).toBeInTheDocument();
      });
    });

    it("should render a test button for voice preview", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        const testButton = screen.getByRole("button", { name: /test voice/i });
        expect(testButton).toBeInTheDocument();
      });
    });
  });

  describe("provider status", () => {
    it("should fetch health status on mount", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(mockGetHealthStatus).toHaveBeenCalled();
      });
    });

    it("should display STT provider status", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Speech-to-Text Provider")).toBeInTheDocument();
      });
    });

    it("should display TTS provider status", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Text-to-Speech Provider")).toBeInTheDocument();
      });
    });

    it("should show active indicator when provider is available", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        const statusSection = screen.getByTestId("provider-status");
        const activeIndicators = within(statusSection).getAllByTestId("status-active");
        expect(activeIndicators.length).toBe(2);
      });
    });

    it("should show inactive indicator when provider is unavailable", async () => {
      mockGetHealthStatus.mockResolvedValueOnce({
        data: {
          stt: { available: false },
          tts: { available: true },
        },
      });

      render(<SpeechSettings />);

      await waitFor(() => {
        const statusSection = screen.getByTestId("provider-status");
        const inactiveIndicators = within(statusSection).getAllByTestId("status-inactive");
        expect(inactiveIndicators.length).toBe(1);
      });
    });

    it("should handle health check error gracefully", async () => {
      mockGetHealthStatus.mockRejectedValueOnce(new Error("Service unavailable"));

      render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText(/unable to check provider status/i)).toBeInTheDocument();
      });
    });
  });

  describe("PDA-friendly design", () => {
    it("should not use aggressive red colors", async () => {
      const { container } = render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Speech Settings")).toBeInTheDocument();
      });

      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-/);
        }
      });
    });

    it("should not use demanding language", async () => {
      const { container } = render(<SpeechSettings />);

      await waitFor(() => {
        expect(screen.getByText("Speech Settings")).toBeInTheDocument();
      });

      const text = container.textContent;
      const demandingWords = [
        "OVERDUE",
        "URGENT",
        "MUST DO",
        "CRITICAL",
        "REQUIRED",
        "YOU NEED TO",
      ];
      for (const word of demandingWords) {
        expect(text.toUpperCase()).not.toContain(word);
      }
    });

    it("should use descriptive section headers", async () => {
      render(<SpeechSettings />);

      await waitFor(() => {
        // Check for descriptive subtext under section headers
        expect(screen.getByText("Configure voice input preferences")).toBeInTheDocument();
        expect(screen.getByText("Configure voice output preferences")).toBeInTheDocument();
      });
    });
  });
});
