import { beforeEach, describe, expect, it, vi } from 'vitest';

vi.mock('../../../shared/state', async () => {
  const sendMessage = vi.fn();
  return { sendMessage, popOutToTab: vi.fn(), isInTab: vi.fn(() => false), openVaultTab: vi.fn() };
});

import { openGeneratorPanel, closeGeneratorPanel, isGeneratorPanelOpen } from '../generator-panel';
import { sendMessage } from '../../../shared/state';
import type { GeneratorRequest } from '../../../shared/types';

const DEFAULT_REQ: GeneratorRequest = {
  kind: 'random',
  length: 20,
  classes: { lower: true, upper: true, digits: true, symbols: true },
  symbol_charset: { kind: 'safe_only' },
};

function setupMount(): { parent: HTMLElement; trigger: HTMLElement } {
  document.body.innerHTML = `
    <div id="parent">
      <button id="trigger" aria-expanded="false">✨</button>
    </div>
  `;
  return {
    parent: document.getElementById('parent')!,
    trigger: document.getElementById('trigger')!,
  };
}

describe('generator-panel', () => {
  beforeEach(() => {
    vi.mocked(sendMessage).mockReset();
    vi.mocked(sendMessage).mockResolvedValue({ ok: true, data: { password: 'Kj7%pW@2xNq!8rMvT' } });
  });

  it('opens a panel with Random kind by default', async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 200));
    expect(document.querySelector('.gen-panel')).not.toBeNull();
    expect(document.querySelector('#gen-kind-random')?.classList.contains('active')).toBe(true);
  });

  it('sends generate_password on knob change (debounced)', async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 200));
    const slider = document.querySelector('#gen-length') as HTMLInputElement;
    slider.value = '32';
    slider.dispatchEvent(new Event('input', { bubbles: true }));
    await new Promise((r) => setTimeout(r, 200));
    const calls = vi.mocked(sendMessage).mock.calls.filter(
      ([msg]) => (msg as { type: string }).type === 'generate_password',
    );
    const latest = calls[calls.length - 1]![0] as { request: GeneratorRequest };
    expect(latest.request.kind).toBe('random');
    if (latest.request.kind === 'random') {
      expect(latest.request.length).toBe(32);
    }
  });

  it('BIP39 toggle swaps to generate_passphrase', async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 200));
    (document.getElementById('gen-kind-bip39') as HTMLButtonElement).click();
    await new Promise((r) => setTimeout(r, 200));
    const calls = vi.mocked(sendMessage).mock.calls;
    expect(calls.some(([msg]) => (msg as { type: string }).type === 'generate_passphrase')).toBe(true);
  });

  it('use-this-value invokes onPicked with current preview and closes', async () => {
    const { parent, trigger } = setupMount();
    const onPicked = vi.fn();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked });
    await new Promise((r) => setTimeout(r, 200));
    (document.querySelector('#gen-use') as HTMLButtonElement).click();
    expect(onPicked).toHaveBeenCalledWith('Kj7%pW@2xNq!8rMvT');
    expect(document.querySelector('.gen-panel')).toBeNull();
  });

  it('save-as-default sends update_vault_settings with the current request', async () => {
    vi.mocked(sendMessage).mockImplementation(async (msg: any) => {
      if (msg.type === 'generate_password') return { ok: true, data: { password: 'abc' } };
      if (msg.type === 'get_vault_settings') {
        return { ok: true, data: { settings: {
          trash_retention: { kind: 'days', value: 30 },
          field_history_retention: { kind: 'forever' },
          generator_defaults: DEFAULT_REQ,
          attachment_caps: {},
          autofill_origin_acks: {},
        } } };
      }
      if (msg.type === 'update_vault_settings') return { ok: true };
      return { ok: false, error: 'unhandled' };
    });
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 200));
    (document.querySelector('#gen-save-default') as HTMLButtonElement).click();
    await new Promise((r) => setTimeout(r, 50));
    const updateCall = vi.mocked(sendMessage).mock.calls.find(
      ([m]) => (m as any).type === 'update_vault_settings',
    );
    expect(updateCall).toBeDefined();
    const msg = updateCall![0] as { settings: { generator_defaults: GeneratorRequest } };
    expect(msg.settings.generator_defaults.kind).toBe('random');
  });

  it('disables use-button when no char class selected (Random)', async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 200));
    for (const id of ['gen-lower', 'gen-upper', 'gen-digits', 'gen-symbols']) {
      const cb = document.getElementById(id) as HTMLInputElement;
      cb.checked = false;
      cb.dispatchEvent(new Event('change', { bubbles: true }));
    }
    const useBtn = document.querySelector('#gen-use') as HTMLButtonElement;
    expect(useBtn.disabled).toBe(true);
  });

  it('closeGeneratorPanel removes the DOM + handlers', async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 200));
    closeGeneratorPanel();
    expect(document.querySelector('.gen-panel')).toBeNull();
  });

  it('sets aria-expanded on the trigger when opened', async () => {
    const { parent, trigger } = setupMount();
    expect(trigger.getAttribute('aria-expanded')).toBe('false');
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    expect(trigger.getAttribute('aria-expanded')).toBe('true');
    closeGeneratorPanel();
    expect(trigger.getAttribute('aria-expanded')).toBe('false');
  });

  it('auto-generates a preview on open', async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 200));
    const calls = vi.mocked(sendMessage).mock.calls.filter(
      ([msg]) => (msg as { type: string }).type === 'generate_password',
    );
    expect(calls.length).toBeGreaterThan(0);
  });

  it('Escape key closes the panel', async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'fill-field', onPicked: vi.fn() });
    await new Promise((r) => setTimeout(r, 50));
    expect(isGeneratorPanelOpen()).toBe(true);
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
    expect(isGeneratorPanelOpen()).toBe(false);
    expect(document.querySelector('.gen-panel')).toBeNull();
  });

  it("configure-defaults context renders only the save-default action (no use/cancel)", async () => {
    const { parent, trigger } = setupMount();
    openGeneratorPanel({ parent, trigger, initial: DEFAULT_REQ, context: 'configure-defaults' });
    await new Promise((r) => setTimeout(r, 50));
    expect(document.querySelector('#gen-save-default')).not.toBeNull();
    expect(document.querySelector('#gen-use')).toBeNull();
    expect(document.querySelector('#gen-cancel')).toBeNull();
  });
});
