import { describe, expect, it, vi, beforeEach } from 'vitest';
import type { IncomingMessage, ServerResponse } from 'http';

import { FeishuAdapter } from './adapter.js';

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

function jsonResponse(body: unknown, status = 200): Response {
  return {
    ok: status >= 200 && status < 300,
    status,
    json: async () => body,
    text: async () => JSON.stringify(body),
  } as Response;
}

function mockReq(body: string): IncomingMessage {
  const req = {
    headers: {},
    on(event: string, handler: (...args: unknown[]) => void) {
      if (event === 'data') {
        handler(Buffer.from(body, 'utf8'));
      }
      if (event === 'end') {
        handler();
      }
      return this;
    },
    off: () => req,
    destroy: () => undefined,
  } as unknown as IncomingMessage;
  return req;
}

function mockRes() {
  const state = { statusCode: 0, body: '' };
  const res = {
    writeHead: (code: number) => {
      state.statusCode = code;
    },
    end: (chunk?: string) => {
      state.body = chunk ?? '';
    },
  } as unknown as ServerResponse;
  return { res, state };
}

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

  it('has name feishu and starts disconnected', () => {
    const adapter = new FeishuAdapter({
      appId: 'app-id',
      appSecret: 'app-secret',
    });
    expect(adapter.name).toBe('feishu');
    expect(adapter.status).toBe('disconnected');
  });

  it('send fetches token and posts message', async () => {
    const adapter = new FeishuAdapter({
      appId: 'app-id',
      appSecret: 'app-secret',
    });
    await adapter.connect();

    mockFetch.mockImplementation(async (url: string) => {
      if (url.includes('/tenant_access_token/internal')) {
        return jsonResponse({ code: 0, tenant_access_token: 'tenant-token', expire: 7200 });
      }
      if (url.includes('/im/v1/messages?receive_id_type=chat_id')) {
        return jsonResponse({ code: 0, msg: 'ok' });
      }
      throw new Error(`Unexpected fetch URL: ${url}`);
    });

    await adapter.send('oc_xxx_chat', { text: 'hello feishu' });
    expect(mockFetch).toHaveBeenCalledTimes(2);
  });

  it('handleRequest returns challenge for url verification', async () => {
    const adapter = new FeishuAdapter({
      appId: 'app-id',
      appSecret: 'app-secret',
      webhookToken: 'verify-token',
    });
    const body = JSON.stringify({
      type: 'url_verification',
      challenge: 'challenge-token',
    });
    const req = mockReq(body);
    const { res, state } = mockRes();

    await adapter.handleRequest(req, res);
    expect(state.statusCode).toBe(200);
    expect(state.body).toContain('challenge-token');
  });

  it('handleEvent forwards text message with reply metadata', async () => {
    const adapter = new FeishuAdapter({
      appId: 'app-id',
      appSecret: 'app-secret',
      webhookToken: 'verify-token',
      requireMention: false,
    });
    const inbound: Array<{ channel: string; senderId: string; text: string }> = [];
    adapter.onMessage((msg) => inbound.push({ channel: msg.channel, senderId: msg.senderId, text: msg.text }));

    await adapter.handleEvent({
      header: { event_type: 'im.message.receive_v1', token: 'verify-token' },
      event: {
        sender: { sender_id: { open_id: 'ou_123' } },
        message: {
          message_id: 'om_1',
          chat_id: 'oc_123',
          chat_type: 'group',
          message_type: 'text',
          content: JSON.stringify({ text: 'ping' }),
        },
      },
    });

    expect(inbound).toEqual([{ channel: 'feishu', senderId: 'ou_123', text: 'ping' }]);
  });

  it('enforces webhook token on event ingress', async () => {
    const adapter = new FeishuAdapter({
      appId: 'app-id',
      appSecret: 'app-secret',
      webhookToken: 'verify-token',
    });

    const body = JSON.stringify({
      header: { event_type: 'im.message.receive_v1', token: 'wrong-token' },
      event: {
        sender: { sender_id: { open_id: 'ou_123' } },
        message: {
          message_id: 'om_1',
          chat_id: 'oc_123',
          chat_type: 'group',
          message_type: 'text',
          content: JSON.stringify({ text: 'ping' }),
        },
      },
    });
    const req = mockReq(body);
    const { res, state } = mockRes();

    await adapter.handleRequest(req, res);
    expect(state.statusCode).toBe(401);
  });
});
