import { render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import userEvent from "@testing-library/user-event";
import AppSettingsScreen from "#/routes/app-settings";
import OpenHands from "#/api/open-hands";
import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers";
import { AvailableLanguages } from "#/i18n";
import * as CaptureConsent from "#/utils/handle-capture-consent";
import * as ToastHandlers from "#/utils/custom-toast-handlers";

const renderAppSettingsScreen = () =>
  render(<AppSettingsScreen />, {
    wrapper: ({ children }) => (
      <QueryClientProvider client={new QueryClient()}>
        {children}
      </QueryClientProvider>
    ),
  });

describe("Content", () => {
  it("should render the screen", () => {
    renderAppSettingsScreen();
    screen.getByTestId("app-settings-screen");
  });

  it("should render the correct default values", async () => {
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue({
      ...MOCK_DEFAULT_USER_SETTINGS,
      language: "no",
      user_consents_to_analytics: true,
      enable_sound_notifications: true,
    });

    renderAppSettingsScreen();

    await waitFor(() => {
      const language = screen.getByTestId("language-input");
      const analytics = screen.getByTestId("enable-analytics-switch");
      const sound = screen.getByTestId("enable-sound-notifications-switch");

      expect(language).toHaveValue("Norsk");
      expect(analytics).toBeChecked();
      expect(sound).toBeChecked();
    });
  });

  it("should render the language options", async () => {
    renderAppSettingsScreen();

    const language = await screen.findByTestId("language-input");
    await userEvent.click(language);

    AvailableLanguages.forEach((lang) => {
      const option = screen.getByText(lang.label);
      expect(option).toBeInTheDocument();
    });
  });
});

describe("Form submission", () => {
  afterEach(() => {
    vi.clearAllMocks();
  });

  it("should submit the form with the correct values", async () => {
    const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);

    renderAppSettingsScreen();

    const language = await screen.findByTestId("language-input");
    const analytics = await screen.findByTestId("enable-analytics-switch");
    const sound = await screen.findByTestId(
      "enable-sound-notifications-switch",
    );

    expect(language).toHaveValue("English");
    expect(analytics).not.toBeChecked();
    expect(sound).not.toBeChecked();

    // change language
    await userEvent.click(language);
    const norsk = screen.getByText("Norsk");
    await userEvent.click(norsk);
    expect(language).toHaveValue("Norsk");

    // toggle options
    await userEvent.click(analytics);
    expect(analytics).toBeChecked();
    await userEvent.click(sound);
    expect(sound).toBeChecked();

    // submit the form
    const submit = await screen.findByTestId("submit-button");
    await userEvent.click(submit);
    expect(saveSettingsSpy).toHaveBeenCalledWith(
      expect.objectContaining({
        language: "no",
        user_consents_to_analytics: true,
        enable_sound_notifications: true,
      }),
    );
  });

  it("should only enable the submit button when there are changes", async () => {
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);

    renderAppSettingsScreen();

    const submit = await screen.findByTestId("submit-button");
    expect(submit).toBeDisabled();

    // Language check
    const language = await screen.findByTestId("language-input");
    await userEvent.click(language);
    const norsk = screen.getByText("Norsk");
    await userEvent.click(norsk);
    expect(submit).not.toBeDisabled();

    await userEvent.click(language);
    const english = screen.getByText("English");
    await userEvent.click(english);
    expect(submit).toBeDisabled();

    // Analytics check
    const analytics = await screen.findByTestId("enable-analytics-switch");
    await userEvent.click(analytics);
    expect(submit).not.toBeDisabled();

    await userEvent.click(analytics);
    expect(submit).toBeDisabled();

    // Sound check
    const sound = await screen.findByTestId(
      "enable-sound-notifications-switch",
    );
    await userEvent.click(sound);
    expect(submit).not.toBeDisabled();

    await userEvent.click(sound);
    expect(submit).toBeDisabled();
  });

  it("should call handleCaptureConsents with true when the analytics switch is toggled", async () => {
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);

    const handleCaptureConsentsSpy = vi.spyOn(
      CaptureConsent,
      "handleCaptureConsent",
    );

    renderAppSettingsScreen();

    const analytics = await screen.findByTestId("enable-analytics-switch");
    const submit = await screen.findByTestId("submit-button");

    await userEvent.click(analytics);
    await userEvent.click(submit);

    await waitFor(() =>
      expect(handleCaptureConsentsSpy).toHaveBeenCalledWith(true),
    );
  });

  it("should call handleCaptureConsents with false when the analytics switch is toggled", async () => {
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue({
      ...MOCK_DEFAULT_USER_SETTINGS,
      user_consents_to_analytics: true,
    });

    const handleCaptureConsentsSpy = vi.spyOn(
      CaptureConsent,
      "handleCaptureConsent",
    );

    renderAppSettingsScreen();

    const analytics = await screen.findByTestId("enable-analytics-switch");
    const submit = await screen.findByTestId("submit-button");

    await userEvent.click(analytics);
    await userEvent.click(submit);

    await waitFor(() =>
      expect(handleCaptureConsentsSpy).toHaveBeenCalledWith(false),
    );
  });

  // flaky test
  it.skip("should disable the button when submitting changes", async () => {
    renderAppSettingsScreen();

    const submit = await screen.findByTestId("submit-button");
    expect(submit).toBeDisabled();

    const sound = await screen.findByTestId(
      "enable-sound-notifications-switch",
    );
    await userEvent.click(sound);
    expect(submit).not.toBeDisabled();

    // submit the form
    await userEvent.click(submit);

    expect(submit).toHaveTextContent("Saving...");
    expect(submit).toBeDisabled();

    await waitFor(() => expect(submit).toHaveTextContent("Save"));
  });

  it("should disable the button after submitting changes", async () => {
    const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);

    renderAppSettingsScreen();

    const submit = await screen.findByTestId("submit-button");
    expect(submit).toBeDisabled();

    const sound = await screen.findByTestId(
      "enable-sound-notifications-switch",
    );
    await userEvent.click(sound);
    expect(submit).not.toBeDisabled();

    // submit the form
    await userEvent.click(submit);
    expect(saveSettingsSpy).toHaveBeenCalled();

    await waitFor(() => expect(submit).toBeDisabled());
  });
});

describe("Status toasts", () => {
  it("should call displaySuccessToast when the settings are saved", async () => {
    const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);

    const displaySuccessToastSpy = vi.spyOn(
      ToastHandlers,
      "displaySuccessToast",
    );

    renderAppSettingsScreen();

    // Toggle setting to change
    const sound = await screen.findByTestId(
      "enable-sound-notifications-switch",
    );
    await userEvent.click(sound);

    const submit = await screen.findByTestId("submit-button");
    await userEvent.click(submit);

    expect(saveSettingsSpy).toHaveBeenCalled();
    await waitFor(() => expect(displaySuccessToastSpy).toHaveBeenCalled());
  });

  it("should call displayErrorToast when the settings fail to save", async () => {
    const saveSettingsSpy = vi.spyOn(OpenHands, "saveSettings");
    const getSettingsSpy = vi.spyOn(OpenHands, "getSettings");
    getSettingsSpy.mockResolvedValue(MOCK_DEFAULT_USER_SETTINGS);

    const displayErrorToastSpy = vi.spyOn(ToastHandlers, "displayErrorToast");

    saveSettingsSpy.mockRejectedValue(new Error("Failed to save settings"));

    renderAppSettingsScreen();

    // Toggle setting to change
    const sound = await screen.findByTestId(
      "enable-sound-notifications-switch",
    );
    await userEvent.click(sound);

    const submit = await screen.findByTestId("submit-button");
    await userEvent.click(submit);

    expect(saveSettingsSpy).toHaveBeenCalled();
    expect(displayErrorToastSpy).toHaveBeenCalled();
  });
});
