import { beforeEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_THREAD_STATE, useChatStore } from '../chatStore';

const NOW = 1700000000000;

function serverMsg(id: string, opts?: { catId?: string; mentionsUser?: boolean }) {
  return {
    id,
    content: `msg-${id}`,
    catId: opts?.catId ?? null,
    timestamp: NOW,
    ...(opts?.mentionsUser ? { mentionsUser: true } : {}),
  };
}

describe('markMessagesDelivered mentionsUser notification', () => {
  let notifySpy: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    notifySpy = vi.fn();
    vi.stubGlobal('Notification', Object.assign(notifySpy, { permission: 'granted' }));
    useChatStore.setState({
      currentThreadId: 'thread-1',
      messages: [],
      threadStates: {},
    });
  });

  it('active thread + blurred: fires notification for new mention', () => {
    vi.stubGlobal('document', { hasFocus: () => false });

    useChatStore
      .getState()
      .markMessagesDelivered('thread-1', ['m1'], NOW + 1, [serverMsg('m1', { catId: 'opus', mentionsUser: true })]);

    expect(notifySpy).toHaveBeenCalledTimes(1);
    expect(notifySpy.mock.calls[0][0]).toContain('@');
  });

  it('active thread + focused: does NOT fire notification', () => {
    vi.stubGlobal('document', { hasFocus: () => true });

    useChatStore
      .getState()
      .markMessagesDelivered('thread-1', ['m1'], NOW + 1, [serverMsg('m1', { catId: 'opus', mentionsUser: true })]);

    expect(notifySpy).not.toHaveBeenCalled();
  });

  it('background thread: fires notification for new mention', () => {
    useChatStore
      .getState()
      .markMessagesDelivered('thread-bg', ['m1'], NOW + 1, [serverMsg('m1', { catId: 'opus', mentionsUser: true })]);

    expect(notifySpy).toHaveBeenCalledTimes(1);
    const ts = useChatStore.getState().threadStates['thread-bg'];
    expect(ts?.hasUserMention).toBe(true);
  });

  it('background thread: duplicate delivery does NOT re-light badge', () => {
    useChatStore.setState({
      threadStates: {
        'thread-bg': {
          ...DEFAULT_THREAD_STATE,
          messages: [
            {
              id: 'm1',
              type: 'assistant' as const,
              content: 'msg-m1',
              timestamp: NOW,
              catId: 'opus',
              mentionsUser: true,
              deliveredAt: NOW,
            },
          ],
          hasUserMention: false,
        },
      },
    });

    useChatStore
      .getState()
      .markMessagesDelivered('thread-bg', ['m1'], NOW + 1, [serverMsg('m1', { catId: 'opus', mentionsUser: true })]);

    expect(notifySpy).not.toHaveBeenCalled();
    expect(useChatStore.getState().threadStates['thread-bg']?.hasUserMention).toBe(false);
  });

  it('active thread: merges queued callback delivery into existing rich-block placeholder', () => {
    const audioBlock = {
      id: 'voice-1',
      kind: 'audio' as const,
      v: 1 as const,
      url: '/api/tts/audio/voice-1.wav',
      text: '五一快乐',
    };

    useChatStore.setState({
      currentThreadId: 'thread-1',
      messages: [
        {
          id: 'msg-inv-1-opus',
          type: 'assistant',
          catId: 'opus',
          content: '',
          origin: 'stream',
          isStreaming: true,
          timestamp: NOW - 10,
          extra: {
            stream: { invocationId: 'inv-1' },
            rich: { v: 1, blocks: [audioBlock] },
          },
        },
      ],
      threadStates: {},
    });

    useChatStore.getState().markMessagesDelivered('thread-1', ['server-msg-1'], NOW + 1, [
      {
        id: 'server-msg-1',
        content: '五一快乐',
        catId: 'opus',
        timestamp: NOW,
        origin: 'callback',
        extra: {
          stream: { invocationId: 'inv-1' },
          rich: { v: 1, blocks: [audioBlock] },
        },
      },
    ]);

    const messages = useChatStore.getState().messages;
    expect(messages).toHaveLength(1);
    expect(messages[0]).toMatchObject({
      id: 'server-msg-1',
      type: 'assistant',
      catId: 'opus',
      content: '五一快乐',
      origin: 'callback',
      isStreaming: false,
      deliveredAt: NOW + 1,
    });
    expect(messages[0]?.extra?.rich?.blocks).toEqual([audioBlock]);
  });
});
