/**
 * ThemeSwitcher Component Test Coverage
 */

import { ref } from 'vue';
import { flushPromises, mount } from '@vue/test-utils';

import { createTestingPinia } from '@pinia/testing';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import type { Mock, MockInstance } from 'vitest';

const mockFormUrlPostRes = vi.fn();
const mockFormUrlPost = vi.fn(() => ({ res: mockFormUrlPostRes }));
const mockFormUrl = vi.fn(() => ({ post: mockFormUrlPost }));

vi.doMock('~/composables/services/webgui', () => ({
  WebguiUpdate: {
    formUrl: mockFormUrl,
  },
}));

const mockServerStore = {
  csrf: ref('mock-csrf-token-123'),
};
vi.mock('~/store/server', () => ({
  useServerStore: () => mockServerStore,
}));

vi.stubGlobal('sessionStorage', {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
});
vi.stubGlobal('localStorage', {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
});

const mockLocation = {
  reload: vi.fn(),
};

vi.stubGlobal('location', mockLocation);

describe('ThemeSwitcher.standalone.vue', () => {
  let consoleDebugSpy: MockInstance;
  let consoleLogSpy: MockInstance;
  let consoleErrorSpy: MockInstance;
  let ThemeSwitcher: unknown;

  beforeEach(async () => {
    ThemeSwitcher = (await import('~/components/ThemeSwitcher.standalone.vue')).default;

    vi.useFakeTimers();
    vi.clearAllMocks();
    vi.restoreAllMocks();

    (sessionStorage.getItem as Mock).mockReturnValue(null);
    (localStorage.getItem as Mock).mockReturnValue(null);

    mockFormUrl.mockClear();
    mockFormUrlPost.mockClear();
    mockFormUrlPostRes.mockClear();

    mockLocation.reload.mockClear();

    consoleDebugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
    consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
    consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
  });

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

  it('does not render if enableThemeSwitcher is not set in storage', () => {
    const wrapper = mount(ThemeSwitcher, {
      props: { current: 'azure' },
      global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
    });

    expect(wrapper.find('select').exists()).toBe(false);
  });

  it('renders if enableThemeSwitcher is set in sessionStorage', () => {
    (sessionStorage.getItem as Mock).mockImplementation((key: string) =>
      key === 'enableThemeSwitcher' ? 'true' : null
    );
    const wrapper = mount(ThemeSwitcher, {
      props: { current: 'azure' },
      global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
    });

    expect(wrapper.find('select').exists()).toBe(true);
  });

  it('renders if enableThemeSwitcher is set in localStorage', () => {
    (localStorage.getItem as Mock).mockImplementation((key: string) =>
      key === 'enableThemeSwitcher' ? 'true' : null
    );

    const wrapper = mount(ThemeSwitcher, {
      props: { current: 'azure' },
      global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
    });

    expect(wrapper.find('select').exists()).toBe(true);
  });

  describe('when rendered', () => {
    beforeEach(() => {
      // Ensure component renders for subsequent tests
      (sessionStorage.getItem as Mock).mockImplementation((key: string) =>
        key === 'enableThemeSwitcher' ? 'true' : null
      );
      // No need to re-import ThemeSwitcher here, already done in outer beforeEach
    });

    it('renders default theme options when themes prop is not provided', () => {
      const wrapper = mount(ThemeSwitcher, {
        props: { current: 'azure' },
        global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
      });
      const options = wrapper.findAll('option');

      expect(options).toHaveLength(4);
      expect(options.map((o) => o.attributes('value'))).toEqual(['azure', 'black', 'gray', 'white']);
    });

    it('renders theme options from themes prop (array)', () => {
      const customThemes = ['red', 'blue'];
      const wrapper = mount(ThemeSwitcher, {
        props: { current: 'red', themes: customThemes },
        global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
      });
      const options = wrapper.findAll('option');

      expect(options).toHaveLength(2);
      expect(options.map((o) => o.attributes('value'))).toEqual(customThemes);
    });

    it('renders theme options from themes prop (JSON string)', () => {
      const customThemes = ['green', 'yellow'];
      const jsonThemes = JSON.stringify(customThemes);
      const wrapper = mount(ThemeSwitcher, {
        props: { current: 'green', themes: jsonThemes },
        global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
      });
      const options = wrapper.findAll('option');

      expect(options).toHaveLength(2);
      expect(options.map((o) => o.attributes('value'))).toEqual(customThemes);
    });

    it('sets the initial value based on the current prop', () => {
      const currentTheme = 'black';
      const wrapper = mount(ThemeSwitcher, {
        props: { current: currentTheme },
        global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
      });
      const select = wrapper.find('select');

      expect((select.element as HTMLSelectElement).value).toBe(currentTheme);
    });

    describe('handleThemeChange', () => {
      it('does nothing if the selected theme is the current theme', async () => {
        const currentTheme = 'azure';
        const wrapper = mount(ThemeSwitcher, {
          props: { current: currentTheme },
          global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
        });
        const select = wrapper.find('select');

        await select.setValue(currentTheme);

        expect(mockFormUrl).not.toHaveBeenCalled();
        expect(consoleDebugSpy).toHaveBeenCalledWith('[ThemeSwitcher.setTheme] Theme is already set');
        expect(select.attributes('disabled')).toBeUndefined();
      });

      it('calls WebguiUpdate and reloads on selecting a new theme', async () => {
        const currentTheme = 'azure';
        const newTheme = 'black';
        const wrapper = mount(ThemeSwitcher, {
          props: { current: currentTheme },
          global: { plugins: [createTestingPinia({ createSpy: vi.fn })] },
        });
        const select = wrapper.find('select');

        // Mock the successful response callback chain
        const mockResCallback = vi.fn();

        mockFormUrlPostRes.mockImplementation((cb: () => void) => {
          cb();
          mockResCallback();
        });

        await select.setValue(newTheme);

        // Assertions before timeout
        expect(consoleDebugSpy).toHaveBeenCalledWith('[ThemeSwitcher.setTheme] Submitting form');
        expect(mockFormUrl).toHaveBeenCalledTimes(1);
        expect(mockFormUrl).toHaveBeenCalledWith({
          csrf_token: 'mock-csrf-token-123',
          '#file': 'dynamix/dynamix.cfg',
          '#section': 'display',
          theme: newTheme,
        });
        expect(mockFormUrlPost).toHaveBeenCalledTimes(1);
        expect(mockFormUrlPostRes).toHaveBeenCalledTimes(1);
        expect(select.attributes('disabled')).toBeDefined();
        expect(mockLocation.reload).not.toHaveBeenCalled();

        expect(mockResCallback).toHaveBeenCalledTimes(1);
        expect(consoleLogSpy).toHaveBeenCalledWith('[ThemeSwitcher.setTheme] Theme updated, reloading…');

        await vi.advanceTimersByTimeAsync(1000);

        expect(mockLocation.reload).toHaveBeenCalledTimes(1);
      });

      it('handles error during WebguiUpdate call', async () => {
        const currentTheme = 'azure';
        const newTheme = 'black';
        const updateError = new Error('Network Error');
        const componentThrownErrorMsg = '[ThemeSwitcher.setTheme] Failed to update theme';
        const mockResWithError = vi.fn(() => {
          throw updateError;
        });
        const mockPostWithError = vi.fn(() => ({ res: mockResWithError }));

        mockFormUrl.mockImplementationOnce(() => ({ post: mockPostWithError }));

        // Mock Vue's global error handler for this specific mount
        const mockErrorHandler = vi.fn();

        const wrapper = mount(ThemeSwitcher, {
          props: { current: currentTheme },
          global: {
            plugins: [createTestingPinia({ createSpy: vi.fn })],
            config: {
              errorHandler: mockErrorHandler,
            },
          },
        });
        const select = wrapper.find('select');

        // Trigger the change that will cause an error
        await select.setValue(newTheme);
        await flushPromises();

        expect(consoleErrorSpy).toHaveBeenCalledWith(
          '[ThemeSwitcher.setTheme] Failed to update theme',
          updateError
        );
        expect(mockErrorHandler).toHaveBeenCalledTimes(1);

        const caughtError = mockErrorHandler.mock.calls[0][0] as Error;

        expect(caughtError).toBeInstanceOf(Error);
        expect(caughtError.message).toBe(componentThrownErrorMsg);
        expect(select.attributes('disabled')).toBeDefined();
        expect(mockLocation.reload).not.toHaveBeenCalled();
      });
    });
  });
});
