import type { ReactNode } from 'react';
import { act, renderHook, waitFor } from '@testing-library/react';
import { QueryClientProvider } from '@tanstack/react-query';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { useAllSettings } from '@/api/queries/useAllSettings';
import { keys } from '@/api/queryKeys';
import { makeTestQueryClient } from '@/test/test-utils';
import { HttpUtil, Msg } from '@/utils';

afterEach(() => {
  vi.restoreAllMocks();
});

describe('useAllSettings', () => {
  it('refreshes cached default settings after a successful save', async () => {
    vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', {}));
    const queryClient = makeTestQueryClient();
    const fetchDefaults = vi
      .fn()
      .mockResolvedValueOnce({ subURI: 'https://example.com/sub/' })
      .mockResolvedValueOnce({ subURI: 'https://example.com/my_custom_path/' });
    const defaultsQuery = {
      queryKey: keys.settings.defaults(),
      queryFn: fetchDefaults,
      staleTime: Infinity,
    };
    const wrapper = ({ children }: { children: ReactNode }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );

    await queryClient.query(defaultsQuery);
    const { result } = renderHook(() => useAllSettings(), { wrapper });

    await waitFor(() => expect(result.current.fetched).toBe(true));
    await act(async () => {
      await result.current.saveAll();
    });

    const defaults = await queryClient.query(defaultsQuery);
    expect(fetchDefaults).toHaveBeenCalledTimes(2);
    expect(defaults.subURI).toBe('https://example.com/my_custom_path/');
  });

  it('keeps cached default settings when a save fails', async () => {
    vi.spyOn(HttpUtil, 'post').mockImplementation(async (url) => {
      if (url === '/panel/api/setting/update') return new Msg(false, 'Save failed');
      return new Msg(true, '', {});
    });
    const queryClient = makeTestQueryClient();
    const fetchDefaults = vi
      .fn()
      .mockResolvedValueOnce({ subURI: 'https://example.com/sub/' })
      .mockResolvedValueOnce({ subURI: 'https://example.com/my_custom_path/' });
    const defaultsQuery = {
      queryKey: keys.settings.defaults(),
      queryFn: fetchDefaults,
      staleTime: Infinity,
    };
    const wrapper = ({ children }: { children: ReactNode }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );

    await queryClient.query(defaultsQuery);
    const { result } = renderHook(() => useAllSettings(), { wrapper });

    await waitFor(() => expect(result.current.fetched).toBe(true));
    await act(async () => {
      await result.current.saveAll();
    });

    const defaults = await queryClient.query(defaultsQuery);
    expect(fetchDefaults).toHaveBeenCalledOnce();
    expect(defaults.subURI).toBe('https://example.com/sub/');
  });

  it('accepts legacy overlength regex settings without logging a response validation warning', async () => {
    const subJsonUserAgentRegex = 'x'.repeat(2_049);
    vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', { subJsonUserAgentRegex }));
    const warning = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
    const queryClient = makeTestQueryClient();
    const wrapper = ({ children }: { children: ReactNode }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );

    const { result } = renderHook(() => useAllSettings(), { wrapper });

    await waitFor(() => expect(result.current.fetched).toBe(true));
    expect(result.current.allSetting.subJsonUserAgentRegex).toBe(subJsonUserAgentRegex);
    expect(warning).not.toHaveBeenCalled();
  });

  it('keeps an edited setting when a refetch returns older server data', async () => {
    const values = [{ webPort: 2053 }, { webPort: 2054 }];
    let index = 0;
    vi.spyOn(HttpUtil, 'post').mockImplementation(async () => new Msg(true, '', values[index++]));
    const queryClient = makeTestQueryClient();
    const wrapper = ({ children }: { children: ReactNode }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );
    const { result } = renderHook(() => useAllSettings(), { wrapper });

    await waitFor(() => expect(result.current.fetched).toBe(true));
    act(() => result.current.updateSetting({ webPort: 3000 }));
    await queryClient.invalidateQueries({ queryKey: keys.settings.all() });

    await waitFor(() => expect(HttpUtil.post).toHaveBeenCalledTimes(2));
    expect(result.current.allSetting.webPort).toBe(3000);
    expect(result.current.saveDisabled).toBe(false);
  });

  it('hydrates redacted secrets after a successful save', async () => {
    let fetchCount = 0;
    vi.spyOn(HttpUtil, 'post').mockImplementation(async (url) => {
      if (url === '/panel/api/setting/all') {
        fetchCount += 1;
        return new Msg(
          true,
          '',
          fetchCount === 1 ? { hasTgBotToken: false } : { hasTgBotToken: true, tgBotToken: '' },
        );
      }
      return new Msg(true, '');
    });
    const queryClient = makeTestQueryClient();
    const wrapper = ({ children }: { children: ReactNode }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );
    const { result } = renderHook(() => useAllSettings(), { wrapper });

    await waitFor(() => expect(result.current.fetched).toBe(true));
    act(() => result.current.updateSetting({ tgBotToken: 'secret' }));
    await act(async () => {
      await result.current.saveAll();
    });

    await waitFor(() => expect(HttpUtil.post).toHaveBeenCalledTimes(3));
    expect(result.current.allSetting.tgBotToken).toBe('');
    expect(result.current.allSetting.hasTgBotToken).toBe(true);
    expect(result.current.saveDisabled).toBe(true);
  });

  it('establishes a saved baseline for a full-payload security save', async () => {
    let fetchCount = 0;
    vi.spyOn(HttpUtil, 'post').mockImplementation(async (url) => {
      if (url === '/panel/api/setting/all') {
        fetchCount += 1;
        return new Msg(
          true,
          '',
          fetchCount === 1 ? { hasTgBotToken: false } : { hasTgBotToken: true, tgBotToken: '' },
        );
      }
      return new Msg(true, '');
    });
    const queryClient = makeTestQueryClient();
    const wrapper = ({ children }: { children: ReactNode }) => (
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    );
    const { result } = renderHook(() => useAllSettings(), { wrapper });

    await waitFor(() => expect(result.current.fetched).toBe(true));
    act(() => result.current.updateSetting({ tgBotToken: 'secret' }));
    await act(async () => {
      await result.current.savePayload({
        ...result.current.allSetting,
        twoFactorEnable: false,
        twoFactorToken: '',
      });
    });

    await waitFor(() => expect(HttpUtil.post).toHaveBeenCalledTimes(3));
    expect(result.current.allSetting.tgBotToken).toBe('');
    expect(result.current.allSetting.hasTgBotToken).toBe(true);
    expect(result.current.saveDisabled).toBe(true);
  });
});
