import { beforeEach, describe, expect, it, vi } from 'vitest';
import { renderSettings } from '../settings';

vi.mock('../../../shared/state', () => ({
  setState: vi.fn(),
  sendMessage: vi.fn(),
  navigate: vi.fn(),
  escapeHtml: (s: string) => s,
  popOutToTab: vi.fn(),
  isInTab: vi.fn(() => false),
  openVaultTab: vi.fn(),
}));

import { sendMessage } from '../../../shared/state';
import { DEFAULT_DIGIT_COLOR, DEFAULT_SYMBOL_COLOR } from '../../../shared/color-scheme';

function mockChromeStorage(initial: Record<string, unknown> = {}) {
  const store: Record<string, unknown> = { ...initial };
  (global as any).chrome = {
    storage: {
      sync: {
        get: vi.fn((key: string) => Promise.resolve(
          key in store ? { [key]: store[key] } : {})),
        set: vi.fn((kv: Record<string, unknown>) => { Object.assign(store, kv); return Promise.resolve(); }),
        remove: vi.fn((key: string) => { delete store[key]; return Promise.resolve(); }),
      },
      local: {
        get: vi.fn(() => Promise.resolve({})),
        set: vi.fn(() => Promise.resolve()),
      },
    },
  };
  return store;
}

// After the Stream B left-nav restructure (bd6a301) and the management-surfaces
// revamp, renderSettings makes these calls in this order:
//   1. is_unlocked                              (gates vault-only sections)
//   2. get_settings + get_blacklist (parallel)  (Autofill is the default section)
function mockDefaultLanding(opts: { unlocked?: boolean } = {}) {
  const unlocked = opts.unlocked ?? false;
  (sendMessage as ReturnType<typeof vi.fn>)
    .mockResolvedValueOnce({ ok: true, data: { unlocked } })
    .mockResolvedValueOnce({ ok: true, data: { settings: { captureEnabled: false, captureStyle: 'bar' } } })
    .mockResolvedValueOnce({ ok: true, data: { blacklist: [] } });
}

async function navigateToDisplay(app: HTMLElement): Promise<void> {
  const btn = app.querySelector<HTMLButtonElement>('[data-section="display"]')!;
  btn.click();
  // Allow renderDisplaySection's async loadColorScheme + DOM writes to settle.
  await new Promise((r) => setTimeout(r, 0));
  await new Promise((r) => setTimeout(r, 0));
}

describe('settings view', () => {
  let app: HTMLElement;

  beforeEach(() => {
    document.body.innerHTML = '<div id="app"></div>';
    app = document.getElementById('app')!;
    (sendMessage as ReturnType<typeof vi.fn>).mockReset();
  });

  it('renders the left-nav with the seven sections', async () => {
    mockChromeStorage();
    mockDefaultLanding();

    await renderSettings(app);

    const sections = ['autofill', 'display', 'security', 'generator', 'retention', 'backup', 'import'];
    for (const s of sections) {
      expect(app.querySelector(`[data-section="${s}"]`)).not.toBeNull();
    }
  });

  it('lands on the Autofill section by default and renders the capture toggle', async () => {
    mockChromeStorage();
    mockDefaultLanding();

    await renderSettings(app);

    expect(sendMessage).toHaveBeenCalledWith({ type: 'is_unlocked' });
    expect(sendMessage).toHaveBeenCalledWith({ type: 'get_settings' });
    expect(sendMessage).toHaveBeenCalledWith({ type: 'get_blacklist' });
    expect(app.querySelector<HTMLInputElement>('#capture-enabled')).not.toBeNull();
  });

  it('toggling capture-enabled sends an update_settings message', async () => {
    mockChromeStorage();
    mockDefaultLanding();
    (sendMessage as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ ok: true });

    await renderSettings(app);
    const cb = app.querySelector<HTMLInputElement>('#capture-enabled')!;
    cb.checked = true;
    cb.dispatchEvent(new Event('change'));
    await new Promise((r) => setTimeout(r, 0));

    expect(sendMessage).toHaveBeenCalledWith({
      type: 'update_settings',
      settings: { captureEnabled: true },
    });
  });
});

describe('settings Display section', () => {
  let app: HTMLElement;

  beforeEach(() => {
    document.body.innerHTML = '<div id="app"></div>';
    app = document.getElementById('app')!;
    (sendMessage as ReturnType<typeof vi.fn>).mockReset();
  });

  it('renders digit and symbol color pickers with default values when storage is empty', async () => {
    mockChromeStorage();
    mockDefaultLanding();

    await renderSettings(app);
    await navigateToDisplay(app);

    const digitInput = app.querySelector<HTMLInputElement>('#digit-color');
    const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color');
    expect(digitInput).not.toBeNull();
    expect(symbolInput).not.toBeNull();
    expect(digitInput!.value).toBe(DEFAULT_DIGIT_COLOR);
    expect(symbolInput!.value).toBe(DEFAULT_SYMBOL_COLOR);
  });

  it('renders pickers with stored values when storage has a scheme', async () => {
    mockChromeStorage({
      password_display_scheme: { digit_color: '#112233', symbol_color: '#aabbcc' },
    });
    mockDefaultLanding();

    await renderSettings(app);
    await navigateToDisplay(app);

    const digitInput = app.querySelector<HTMLInputElement>('#digit-color');
    const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color');
    expect(digitInput!.value).toBe('#112233');
    expect(symbolInput!.value).toBe('#aabbcc');
  });

  it('renders a color-preview swatch element', async () => {
    mockChromeStorage();
    mockDefaultLanding();

    await renderSettings(app);
    await navigateToDisplay(app);

    expect(app.querySelector('#color-preview')).not.toBeNull();
  });

  it('changing digit color calls saveColorScheme with updated scheme', async () => {
    mockChromeStorage();
    mockDefaultLanding();

    await renderSettings(app);
    await navigateToDisplay(app);

    const digitInput = app.querySelector<HTMLInputElement>('#digit-color')!;
    digitInput.value = '#ff0000';
    digitInput.dispatchEvent(new Event('change'));
    await new Promise((r) => setTimeout(r, 0));

    const syncSet = (global as any).chrome.storage.sync.set as ReturnType<typeof vi.fn>;
    expect(syncSet).toHaveBeenCalledWith(
      expect.objectContaining({
        password_display_scheme: expect.objectContaining({ digit_color: '#ff0000' }),
      }),
    );
  });

  it('changing symbol color calls saveColorScheme with updated scheme', async () => {
    mockChromeStorage();
    mockDefaultLanding();

    await renderSettings(app);
    await navigateToDisplay(app);

    const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color')!;
    symbolInput.value = '#00ff00';
    symbolInput.dispatchEvent(new Event('change'));
    await new Promise((r) => setTimeout(r, 0));

    const syncSet = (global as any).chrome.storage.sync.set as ReturnType<typeof vi.fn>;
    expect(syncSet).toHaveBeenCalledWith(
      expect.objectContaining({
        password_display_scheme: expect.objectContaining({ symbol_color: '#00ff00' }),
      }),
    );
  });

  it('clicking reset calls chrome.storage.sync.remove and restores defaults', async () => {
    mockChromeStorage({
      password_display_scheme: { digit_color: '#112233', symbol_color: '#aabbcc' },
    });
    mockDefaultLanding();

    await renderSettings(app);
    await navigateToDisplay(app);

    const resetBtn = app.querySelector<HTMLButtonElement>('#reset-colors')!;
    resetBtn.click();
    await new Promise((r) => setTimeout(r, 0));
    await new Promise((r) => setTimeout(r, 0));

    const syncRemove = (global as any).chrome.storage.sync.remove as ReturnType<typeof vi.fn>;
    expect(syncRemove).toHaveBeenCalledWith('password_display_scheme');

    const digitInput = app.querySelector<HTMLInputElement>('#digit-color')!;
    const symbolInput = app.querySelector<HTMLInputElement>('#symbol-color')!;
    expect(digitInput.value).toBe(DEFAULT_DIGIT_COLOR);
    expect(symbolInput.value).toBe(DEFAULT_SYMBOL_COLOR);
  });
});
