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

import { BlueBubblesAdapter } from './adapter.js';
import type { InboundMessage } from '../types.js';

const mockFetch = vi.fn();
vi.stubGlobal('fetch', mockFetch);

function makeRes() {
  const state = {
    status: 0,
    body: '',
  };
  return {
    state,
    res: {
      writeHead: (status: number) => {
        state.status = status;
      },
      end: (chunk?: string) => {
        state.body = chunk ?? '';
      },
    },
  };
}

function makeReq(body: unknown, headers?: Record<string, string>) {
  const chunks = [Buffer.from(JSON.stringify(body), 'utf8')];
  return {
    headers: headers ?? {},
    on(event: string, handler: (...args: unknown[]) => void) {
      if (event === 'data') {
        for (const chunk of chunks) {
          handler(chunk);
        }
      }
      if (event === 'end') {
        handler();
      }
      return this;
    },
    off() {
      return this;
    },
    destroy() {
      return this;
    },
  };
}

describe('BlueBubblesAdapter', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockFetch.mockReset();
  });

  it('has name bluebubbles and starts disconnected', () => {
    const adapter = new BlueBubblesAdapter({ endpoint: 'http://localhost:1234', apiKey: 'key' });
    expect(adapter.name).toBe('bluebubbles');
    expect(adapter.status).toBe('disconnected');
  });

  it('send posts text to BlueBubbles API', async () => {
    const adapter = new BlueBubblesAdapter({ endpoint: 'http://localhost:1234', apiKey: 'key' });
    await adapter.connect();
    mockFetch.mockResolvedValue({
      ok: true,
      status: 200,
      text: async () => '',
    } as Response);

    await adapter.send('chat123', { text: 'hello imessage' });

    expect(mockFetch).toHaveBeenCalledTimes(1);
    expect(mockFetch.mock.calls[0]?.[0]).toBe('http://localhost:1234/api/v1/message/text');
    const init = mockFetch.mock.calls[0]?.[1] as RequestInit;
    expect((init.headers as Record<string, string>).Authorization).toBe('Bearer key');
  });

  it('handleEvent forwards inbound message and sets replyPeerId', async () => {
    const adapter = new BlueBubblesAdapter({ endpoint: 'http://localhost:1234', apiKey: 'key', requireMention: false });
    const handler = vi.fn();
    adapter.onMessage(handler);

    await adapter.handleEvent({
      message: {
        guid: 'm1',
        text: 'hello',
        chatGuid: 'chat123',
        sender: { displayName: 'Alice' },
      },
    });

    expect(handler).toHaveBeenCalledTimes(1);
    const msg = handler.mock.calls[0]?.[0] as InboundMessage;
    expect(msg.channel).toBe('bluebubbles');
    expect(msg.senderId).toBe('chat123');
    expect((msg.metadata as Record<string, unknown>).replyPeerId).toBe('chat123');
  });

  it('handleRequest enforces webhook token when configured', async () => {
    const adapter = new BlueBubblesAdapter({
      endpoint: 'http://localhost:1234',
      apiKey: 'key',
      webhookToken: 'secret-token',
      requireMention: false,
    });
    const handler = vi.fn();
    adapter.onMessage(handler);

    const { res, state } = makeRes();
    const req = makeReq({
      message: { text: 'hello', chatGuid: 'chat123' },
    });
    await adapter.handleRequest(req as never, res as never);

    expect(state.status).toBe(401);
    expect(handler).not.toHaveBeenCalled();
  });
});
