import { describe, it, expect, vi } from 'vitest';
import { handlePushEvent, type ClientLike, type PushPayload } from './push-handler';

function makePayload(overrides: Partial<PushPayload> = {}): PushPayload {
  return {
    title: '✅ タスク完了',
    body: 'chat',
    tag: 'task-1-succeeded',
    data: { taskId: 1 },
    ...overrides,
  };
}

function makeClient(): ClientLike {
  return { postMessage: vi.fn() };
}

describe('handlePushEvent (SW ACK dedup)', () => {
  it('shows OS notification immediately when no clients are open', async () => {
    const showNotification = vi.fn().mockResolvedValue(undefined);
    const waitForAck = vi.fn(); // should NOT be called
    const shown = await handlePushEvent(makePayload(), [], { showNotification, waitForAck });
    expect(shown).toBe(true);
    expect(showNotification).toHaveBeenCalledTimes(1);
    expect(waitForAck).not.toHaveBeenCalled();
  });

  it('suppresses OS notification when a client ACKs', async () => {
    const showNotification = vi.fn().mockResolvedValue(undefined);
    const waitForAck = vi.fn().mockResolvedValue(true);
    const clients = [makeClient()];
    const shown = await handlePushEvent(makePayload(), clients, { showNotification, waitForAck });
    expect(shown).toBe(false);
    expect(showNotification).not.toHaveBeenCalled();
    expect(waitForAck).toHaveBeenCalledTimes(1);
  });

  it('shows OS notification when ACK times out (no visible client)', async () => {
    const showNotification = vi.fn().mockResolvedValue(undefined);
    const waitForAck = vi.fn().mockResolvedValue(false);
    const clients = [makeClient()];
    const shown = await handlePushEvent(makePayload(), clients, { showNotification, waitForAck });
    expect(shown).toBe(true);
    expect(showNotification).toHaveBeenCalledTimes(1);
  });

  it('uses defaults for missing title/icon', async () => {
    const showNotification = vi.fn().mockResolvedValue(undefined);
    const waitForAck = vi.fn(); // unused (no clients)
    const payload: PushPayload = { tag: 'x' };
    await handlePushEvent(payload, [], { showNotification, waitForAck });
    expect(showNotification).toHaveBeenCalledWith('MAESTRO', expect.objectContaining({
      icon: '/ui/icon-192.png',
      badge: '/ui/icon-192.png',
      tag: 'x',
    }));
  });

  it('preserves payload.data on the notification', async () => {
    const showNotification = vi.fn().mockResolvedValue(undefined);
    const waitForAck = vi.fn();
    await handlePushEvent(makePayload(), [], { showNotification, waitForAck });
    expect(showNotification).toHaveBeenCalledWith(
      '✅ タスク完了',
      expect.objectContaining({ data: { taskId: 1 } }),
    );
  });

  it('multiple clients: first ACK suppresses', async () => {
    const showNotification = vi.fn();
    const waitForAck = vi.fn().mockResolvedValue(true);
    const clients = [makeClient(), makeClient(), makeClient()];
    const shown = await handlePushEvent(makePayload(), clients, { showNotification, waitForAck });
    expect(shown).toBe(false);
    expect(waitForAck).toHaveBeenCalledWith(clients, expect.any(Object));
  });
});
