import { getChatMessages, sendChatMessage } from '../rest';

// Mock global fetch
global.fetch = jest.fn();

// Mock environment variables
process.env.REACT_APP_API_BASE_URL = 'http://localhost:3000/api';

describe('getChatMessages', () => {
  beforeEach(() => {
    // Clear all mocks before each test
    global.fetch.mockClear();
  });

  test('fetches messages for session channel', async () => {
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => ({
        messages: [
          { id: 'msg-1', message: 'Hello', sender_id: 'user-1' }
        ],
        next: 20
      })
    });

    const result = await getChatMessages({
      channel: 'session',
      sessionId: 'session-abc'
    });

    expect(fetch).toHaveBeenCalledWith(
      expect.stringContaining('/api/chat?channel=session&session_id=session-abc'),
      expect.objectContaining({ method: 'GET' })
    );
    expect(result.messages).toHaveLength(1);
    expect(result.next).toBe(20);
  });

  test('fetches messages for global channel', async () => {
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => ({
        messages: [
          { id: 'msg-1', message: 'Hello world', sender_id: 'user-1' }
        ],
        next: null
      })
    });

    const result = await getChatMessages({
      channel: 'global'
    });

    expect(fetch).toHaveBeenCalledWith(
      expect.stringContaining('/api/chat?channel=global'),
      expect.objectContaining({ method: 'GET' })
    );
    // Should NOT contain session_id in URL
    expect(fetch.mock.calls[0][0]).not.toContain('session_id');
    expect(result.messages).toHaveLength(1);
    expect(result.next).toBeNull();
  });

  test('includes pagination cursor (before)', async () => {
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => ({
        messages: [],
        next: null
      })
    });

    await getChatMessages({
      channel: 'session',
      sessionId: 'session-abc',
      before: 50
    });

    expect(fetch).toHaveBeenCalledWith(
      expect.stringContaining('before=50'),
      expect.any(Object)
    );
  });

  test('throws error on 404', async () => {
    global.fetch.mockResolvedValue({
      ok: false,
      status: 404,
      statusText: 'Not Found'
    });

    await expect(
      getChatMessages({ channel: 'session', sessionId: 'invalid' })
    ).rejects.toThrow();
  });

  test('throws error on 403', async () => {
    global.fetch.mockResolvedValue({
      ok: false,
      status: 403,
      statusText: 'Forbidden'
    });

    await expect(
      getChatMessages({ channel: 'session', sessionId: 'session-abc' })
    ).rejects.toThrow();
  });

  test('throws error on 500', async () => {
    global.fetch.mockResolvedValue({
      ok: false,
      status: 500,
      statusText: 'Internal Server Error'
    });

    await expect(
      getChatMessages({ channel: 'global' })
    ).rejects.toThrow();
  });

  test('includes credentials and content-type headers', async () => {
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => ({ messages: [], next: null })
    });

    await getChatMessages({ channel: 'global' });

    expect(fetch).toHaveBeenCalledWith(
      expect.any(String),
      expect.objectContaining({
        credentials: 'include',
        headers: expect.objectContaining({
          'Content-Type': 'application/json'
        })
      })
    );
  });
});

describe('sendChatMessage', () => {
  beforeEach(() => {
    global.fetch.mockClear();
  });

  test('sends message to session channel', async () => {
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => ({
        message: {
          id: 'msg-new',
          message: 'Hello world',
          sender_id: 'user-1',
          created_at: '2026-01-26T12:00:00Z'
        }
      })
    });

    const result = await sendChatMessage({
      channel: 'session',
      sessionId: 'session-abc',
      message: 'Hello world'
    });

    expect(fetch).toHaveBeenCalledWith(
      expect.stringContaining('/api/chat'),
      expect.objectContaining({
        method: 'POST'
      })
    );
    // Check body content separately (key order doesn't matter)
    const body = JSON.parse(fetch.mock.calls[0][1].body);
    expect(body).toEqual({
      channel: 'session',
      session_id: 'session-abc',
      message: 'Hello world'
    });
    expect(result.message.id).toBe('msg-new');
  });

  test('sends message to global channel', async () => {
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => ({
        message: {
          id: 'msg-global',
          message: 'Hello everyone',
          sender_id: 'user-1',
          created_at: '2026-01-26T12:00:00Z'
        }
      })
    });

    const result = await sendChatMessage({
      channel: 'global',
      message: 'Hello everyone'
    });

    expect(fetch).toHaveBeenCalledWith(
      expect.any(String),
      expect.objectContaining({
        method: 'POST',
        body: JSON.stringify({
          channel: 'global',
          message: 'Hello everyone'
        })
      })
    );
    // Should NOT contain session_id
    const body = JSON.parse(fetch.mock.calls[0][1].body);
    expect(body.session_id).toBeUndefined();
    expect(result.message.id).toBe('msg-global');
  });

  test('throws error on 422 (validation error)', async () => {
    global.fetch.mockResolvedValue({
      ok: false,
      status: 422,
      statusText: 'Unprocessable Entity',
      json: async () => ({ error: 'Message too long' })
    });

    await expect(
      sendChatMessage({
        channel: 'session',
        sessionId: 'abc',
        message: 'x'.repeat(1000)
      })
    ).rejects.toThrow();
  });

  test('throws error on 403', async () => {
    global.fetch.mockResolvedValue({
      ok: false,
      status: 403,
      statusText: 'Forbidden',
      json: async () => ({ error: 'Unauthorized' })
    });

    await expect(
      sendChatMessage({
        channel: 'session',
        sessionId: 'session-abc',
        message: 'Hello'
      })
    ).rejects.toThrow();
  });

  test('throws error on 500', async () => {
    global.fetch.mockResolvedValue({
      ok: false,
      status: 500,
      statusText: 'Internal Server Error',
      json: async () => ({ error: 'Server error' })
    });

    await expect(
      sendChatMessage({
        channel: 'global',
        message: 'Hello'
      })
    ).rejects.toThrow();
  });

  test('includes credentials and content-type headers', async () => {
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => ({ message: { id: 'msg-1' } })
    });

    await sendChatMessage({
      channel: 'global',
      message: 'Test'
    });

    expect(fetch).toHaveBeenCalledWith(
      expect.any(String),
      expect.objectContaining({
        credentials: 'include',
        headers: expect.objectContaining({
          'Content-Type': 'application/json'
        })
      })
    );
  });

  test('handles error response with invalid json gracefully', async () => {
    global.fetch.mockResolvedValue({
      ok: false,
      status: 500,
      statusText: 'Internal Server Error',
      json: async () => {
        throw new Error('Invalid JSON');
      }
    });

    await expect(
      sendChatMessage({
        channel: 'global',
        message: 'Test'
      })
    ).rejects.toThrow();
  });
});
