import { ASCApiError } from '~/core/errors';
import { user11, disconnectClient } from '~/utils/tests';
import { resumeSession, createClient } from '..';
import { setActiveClient } from '../activeClient';

let client: Amity.Client;

const sessionHandler: Amity.SessionHandler = {
  sessionWillRenewAccessToken(_) {
    // do nothing
  },
};

const mockToken = {
  accessToken: 'mock-access-token',
  issuedAt: '2023-01-01T00:00:00Z',
  expiresAt: '2025-01-01T00:00:00Z',
};

const mockUserResponse = {
  data: {
    users: [user11],
  },
};

const onConnect = () =>
  setTimeout(() => {
    const CONNECT_PACKET = { type: 0, nsp: client.ws?.nsp };

    client.ws?.emit('connect');

    // simulate a connection ack packet from server
    client.ws?.io.emit('packet', CONNECT_PACKET);
  }, 50);

describe('resumeSession', () => {
  beforeEach(() => {
    client = createClient('key', 'sg');
    client.mqtt?.connect && (client.mqtt.connect = jest.fn());
    client.mqtt?.subscribe && (client.mqtt.subscribe = jest.fn());
    client.http.get = jest.fn().mockResolvedValueOnce(mockUserResponse);

    setActiveClient(client);
  });

  afterEach(async () => {
    if (client.sessionState === Amity.SessionStates.ESTABLISHED) await disconnectClient();
  });

  test('it should connect client with access token', async () => {
    onConnect().unref();

    const received = await resumeSession(
      { userId: user11.userId, token: mockToken },
      sessionHandler,
    );

    expect(received).toBe(true);
  });

  test('it should establish connection', async () => {
    onConnect().unref();

    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
    const { sessionState } = client;

    expect(sessionState).toBe(Amity.SessionStates.ESTABLISHED);
  });

  test('it should have session state establishing while connecting client', () => {
    resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);

    expect(client.sessionState).toBe(Amity.SessionStates.ESTABLISHING);
  });

  test('it should have session state notLoggedIn on failure', async () => {
    client.http.get = jest
      .fn()
      .mockRejectedValue(
        new ASCApiError('unauthorized', Amity.ServerError.UNAUTHORIZED, Amity.ErrorLevel.FATAL),
      );

    await expect(
      resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler),
    ).rejects.toThrow('unauthorized');
    expect(client.sessionState).toBe(Amity.SessionStates.NOT_LOGGED_IN);
  });

  test('it should set authorization header with token', async () => {
    onConnect().unref();

    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);

    expect(client.http.defaults.headers.common.Authorization).toBe(
      `Bearer ${mockToken.accessToken}`,
    );
    expect(client.upload.defaults.headers.common.Authorization).toBe(
      `Bearer ${mockToken.accessToken}`,
    );
  });

  test('it should set token metadata', async () => {
    onConnect().unref();

    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);

    expect(client.token).toEqual(mockToken);
    expect(client.http.defaults.metadata).toEqual({
      tokenExpiry: mockToken.expiresAt,
      isGlobalBanned: false,
      isUserDeleted: false,
    });
  });

  test('it should call user endpoint to validate token', async () => {
    onConnect().unref();

    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);

    expect(client.http.get).toHaveBeenCalledWith(`/api/v3/users/${user11.userId}`);
  });

  test('it should terminate session on ban', async () => {
    onConnect().unref();

    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);

    // ban user
    client.emitter.emit('user.didGlobalBan', {} as Amity.UserPayload);

    expect(client.sessionState).toBe(Amity.SessionStates.TERMINATED);
  });

  test('it should reset client state when user is already connected with same userId', async () => {
    onConnect().unref();

    // First connection
    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
    expect(client.sessionState).toBe(Amity.SessionStates.ESTABLISHED);
    expect(client.userId).toBe(user11.userId);

    // Mock the HTTP call for second connection
    client.http.get = jest.fn().mockResolvedValueOnce(mockUserResponse);

    // Second connection with same userId should treat as token expiry (preserve cache)
    onConnect().unref();
    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);

    // Verify that the client is properly reset and reconnected
    expect(client.sessionState).toBe(Amity.SessionStates.ESTABLISHED);
    expect(client.userId).toBe(user11.userId);
  });

  test('it should preserve cache when resuming session with same userId', async () => {
    onConnect().unref();

    // First connection - set some cache data
    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);
    if (client.cache) {
      client.cache.data = { test: 'data' };
    }

    // Mock the HTTP call for second connection
    client.http.get = jest.fn().mockResolvedValueOnce(mockUserResponse);

    // Second connection with same userId
    onConnect().unref();
    await resumeSession({ userId: user11.userId, token: mockToken }, sessionHandler);

    // Cache should be preserved when same userId is used
    expect(client.cache?.data).toEqual({ test: 'data' });
  });
});
