/**
 * Tests for the pre-React bootstrap orchestrator.
 *
 * Each step is wrapped in a try/catch so a non-Tauri runtime never breaks
 * startup; the tests pin both the happy path (invoke called with the right
 * payload) and the failure-tolerant path (no throw when invoke or storage
 * misbehaves).
 */
import { installQueueUndoHotkey } from '../store/queueUndoHotkey';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

vi.mock('@tauri-apps/api/core', () => ({
  invoke: vi.fn(),
}));

vi.mock('../store/queueUndoHotkey', () => ({
  installQueueUndoHotkey: vi.fn(),
}));

vi.mock('./windowKind', () => ({
  getWindowKind: vi.fn(() => 'main'),
}));

import { invoke } from '@tauri-apps/api/core';
import { getWindowKind } from './windowKind';
import {
  applyThemeAtStartup,
  installCrossWindowThemeSync,
  pushLoggingModeToBackend,
  pushUserAgentToBackend,
  runPreReactBootstrap,
} from './bootstrap';
import { useThemeStore } from '../store/themeStore';
import { useInstalledThemesStore } from '../store/installedThemesStore';

const ORIGINAL_USER_AGENT = window.navigator.userAgent;

function setUserAgent(ua: string): void {
  Object.defineProperty(window.navigator, 'userAgent', {
    value: ua,
    configurable: true,
  });
}

beforeEach(() => {
  vi.clearAllMocks();
  vi.mocked(invoke).mockResolvedValue(undefined);
  vi.mocked(getWindowKind).mockReturnValue('main');
  localStorage.clear();
  setUserAgent('psysonic-test-ua/1.0');
});

afterEach(() => {
  setUserAgent(ORIGINAL_USER_AGENT);
  document.documentElement.removeAttribute('data-theme');
  document.head.querySelectorAll('style[data-installed-theme]').forEach((el) => el.remove());
});

describe('pushUserAgentToBackend', () => {
  it('forwards the navigator UA to the backend on the main window', () => {
    pushUserAgentToBackend();
    expect(invoke).toHaveBeenCalledWith('set_subsonic_wire_user_agent', {
      userAgent: 'psysonic-test-ua/1.0',
      windowLabel: 'main',
    });
  });

  it('skips the call entirely when the current window is the mini player', () => {
    vi.mocked(getWindowKind).mockReturnValue('mini');
    pushUserAgentToBackend();
    expect(invoke).not.toHaveBeenCalled();
  });

  it('skips when the navigator UA is empty', () => {
    setUserAgent('');
    pushUserAgentToBackend();
    expect(invoke).not.toHaveBeenCalled();
  });

  it('does not throw when invoke rejects (non-Tauri runtime)', () => {
    vi.mocked(invoke).mockRejectedValue(new Error('not tauri'));
    expect(() => pushUserAgentToBackend()).not.toThrow();
  });
});

describe('pushLoggingModeToBackend', () => {
  it('forwards a valid persisted loggingMode to set_logging_mode', () => {
    localStorage.setItem(
      'psysonic-auth',
      JSON.stringify({ state: { loggingMode: 'debug' } }),
    );
    pushLoggingModeToBackend();
    expect(invoke).toHaveBeenCalledWith('set_logging_mode', { mode: 'debug' });
  });

  it.each(['off', 'normal', 'debug'])('accepts mode=%s', mode => {
    localStorage.setItem('psysonic-auth', JSON.stringify({ state: { loggingMode: mode } }));
    pushLoggingModeToBackend();
    expect(invoke).toHaveBeenCalledWith('set_logging_mode', { mode });
  });

  it('skips when no psysonic-auth entry is present', () => {
    pushLoggingModeToBackend();
    expect(invoke).not.toHaveBeenCalled();
  });

  it('skips an unknown mode value', () => {
    localStorage.setItem(
      'psysonic-auth',
      JSON.stringify({ state: { loggingMode: 'verbose' } }),
    );
    pushLoggingModeToBackend();
    expect(invoke).not.toHaveBeenCalled();
  });

  it('skips when state.loggingMode is missing', () => {
    localStorage.setItem('psysonic-auth', JSON.stringify({ state: {} }));
    pushLoggingModeToBackend();
    expect(invoke).not.toHaveBeenCalled();
  });

  it('does not throw on malformed JSON', () => {
    localStorage.setItem('psysonic-auth', '{not json');
    expect(() => pushLoggingModeToBackend()).not.toThrow();
    expect(invoke).not.toHaveBeenCalled();
  });
});

describe('runPreReactBootstrap', () => {
  it('runs all steps in order: warm window-kind cache, push UA, push logging mode, install hotkey', () => {
    localStorage.setItem(
      'psysonic-auth',
      JSON.stringify({ state: { loggingMode: 'normal' } }),
    );

    runPreReactBootstrap();

    expect(getWindowKind).toHaveBeenCalled();
    expect(invoke).toHaveBeenCalledWith('set_subsonic_wire_user_agent', expect.any(Object));
    expect(invoke).toHaveBeenCalledWith('set_logging_mode', { mode: 'normal' });
    expect(installQueueUndoHotkey).toHaveBeenCalledTimes(1);
  });

  it('still installs the hotkey on the mini window even though UA push is skipped', () => {
    vi.mocked(getWindowKind).mockReturnValue('mini');
    runPreReactBootstrap();
    expect(installQueueUndoHotkey).toHaveBeenCalledTimes(1);
    // installQueueUndoHotkey itself early-returns inside playerStore on mini —
    // the bootstrap doesn't second-guess that decision.
  });
});

describe('applyThemeAtStartup', () => {
  const setPersistedTheme = (state: Record<string, unknown>) =>
    localStorage.setItem('psysonic_theme', JSON.stringify({ state, version: 1 }));

  it('does nothing when there is no persisted theme', () => {
    applyThemeAtStartup();
    expect(document.documentElement.getAttribute('data-theme')).toBeNull();
  });

  it('sets data-theme to the active theme (scheduler off)', () => {
    setPersistedTheme({ theme: 'kanagawa-wave', enableThemeScheduler: false });
    applyThemeAtStartup();
    expect(document.documentElement.getAttribute('data-theme')).toBe('kanagawa-wave');
  });

  it('injects installed community themes up front', () => {
    setPersistedTheme({ theme: 'dracula', enableThemeScheduler: false });
    localStorage.setItem('psysonic_installed_themes', JSON.stringify({
      state: { themes: [{ id: 'dracula', name: 'Dracula', author: 'a', version: '1.0.0', description: '', mode: 'dark', css: "[data-theme='dracula']{--accent:#bd93f9;}", installedAt: 0 }] },
      version: 1,
    }));
    applyThemeAtStartup();
    expect(document.head.querySelector('style[data-installed-theme="dracula"]')).not.toBeNull();
    expect(document.documentElement.getAttribute('data-theme')).toBe('dracula');
  });

  it('does not throw on malformed storage', () => {
    localStorage.setItem('psysonic_theme', '{not json');
    expect(() => applyThemeAtStartup()).not.toThrow();
    expect(document.documentElement.getAttribute('data-theme')).toBeNull();
  });
});

describe('installCrossWindowThemeSync', () => {
  it('rehydrates the matching store on a cross-window storage event', () => {
    const themeRehydrate = vi.spyOn(useThemeStore.persist, 'rehydrate').mockResolvedValue(undefined);
    const installedRehydrate = vi.spyOn(useInstalledThemesStore.persist, 'rehydrate').mockResolvedValue(undefined);
    installCrossWindowThemeSync();

    window.dispatchEvent(new StorageEvent('storage', { key: 'psysonic_theme' }));
    expect(themeRehydrate).toHaveBeenCalled();

    window.dispatchEvent(new StorageEvent('storage', { key: 'psysonic_installed_themes' }));
    expect(installedRehydrate).toHaveBeenCalled();

    themeRehydrate.mockClear();
    installedRehydrate.mockClear();
    window.dispatchEvent(new StorageEvent('storage', { key: 'unrelated-key' }));
    expect(themeRehydrate).not.toHaveBeenCalled();
    expect(installedRehydrate).not.toHaveBeenCalled();
  });
});
