import { NetworkType } from '@metamask/controller-utils';
import { NetworkStatus } from '@metamask/network-controller';
import { migrate, version } from './094';

const sentryCaptureExceptionMock = jest.fn();

global.sentry = {
  startSession: jest.fn(),
  endSession: jest.fn(),
  toggleSession: jest.fn(),
  captureException: sentryCaptureExceptionMock,
};

describe('migration #94', () => {
  afterEach(() => {
    jest.resetAllMocks();
  });

  it('should update the version metadata', async () => {
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: {},
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.meta).toStrictEqual({
      version,
    });
  });

  it('should return state unaltered if there is no network controller state', async () => {
    const oldData = {
      other: 'data',
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual(oldData);
  });

  it('should capture an exception if there is no network controller state', async () => {
    const oldData = {
      other: 'data',
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    await migrate(oldStorage);

    expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
    expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
      new Error(`typeof state.NetworkController is undefined`),
    );
  });

  it('should return state unaltered if there is no network controller providerConfig state', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        networkConfigurations: {
          id1: {
            foo: 'bar',
          },
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual(oldData);
  });

  it('should capture an exception if there is no network controller providerConfig state', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        networkConfigurations: {
          id1: {
            foo: 'bar',
          },
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    await migrate(oldStorage);

    expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
    expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
      new Error(`typeof state.NetworkController.providerConfig is undefined`),
    );
  });

  it('should capture an exception if there is no providerConfig.id and no providerConfig.type value in state', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'NET',
          chainId: '0x189123',
          nickname: 'A Network',
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    await migrate(oldStorage);

    expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
    expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
      new Error(
        `typeof state.NetworkController.providerConfig.id is undefined and state.NetworkController.providerConfig.type is undefined`,
      ),
    );
  });

  it('should not capture an exception if there is a providerConfig.id in state', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'NET',
          chainId: '0x189123',
          nickname: 'A Network',
          id: 'foobar',
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    await migrate(oldStorage);

    expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0);
  });

  it(`should capture an exception if there is no providerConfig.id and the providerConfig.type value is ${NetworkType.rpc} in state`, async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'NET',
          chainId: '0x189123',
          nickname: 'A Network',
          type: NetworkType.rpc,
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    await migrate(oldStorage);

    expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(1);
    expect(sentryCaptureExceptionMock).toHaveBeenCalledWith(
      new Error(
        `typeof state.NetworkController.providerConfig.id is undefined and state.NetworkController.providerConfig.type is ${NetworkType.rpc}`,
      ),
    );
  });

  it(`should not capture an exception if there is no providerConfig.id and the providerConfig.type value is not ${NetworkType.rpc} in state`, async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'NET',
          chainId: '0x189123',
          nickname: 'A Network',
          type: 'NOT_AN_RPC_TYPE',
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    await migrate(oldStorage);

    expect(sentryCaptureExceptionMock).toHaveBeenCalledTimes(0);
  });

  it('should return state unaltered if there is a providerConfig.id value in state but it is not a string', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'NET',
          type: NetworkType.rpc,
          chainId: '0x189123',
          nickname: 'A Network',
          id: { not: 'a string' },
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual(oldData);
  });

  it('should return state unaltered if there is a providerConfig.type value in state but it equals NetworkType.rpc and there is no providerConfig.id value in state', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'ETH',
          type: NetworkType.rpc,
          chainId: '0x189123',
          nickname: 'A Network',
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual(oldData);
  });

  it('should add a selectedNetworkClientId property and a default networksMetadata object to the NetworkController state if there is a providerConfig.id value in state and it is a string (and there are no networkStatus or networkDetails values)', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'NET',
          type: NetworkType.rpc,
          chainId: '0x189123',
          nickname: 'A Network',
          id: 'test-network-client-id',
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data.NetworkController).toStrictEqual({
      providerConfig: {
        ticker: 'NET',
        type: NetworkType.rpc,
        chainId: '0x189123',
        nickname: 'A Network',
        id: 'test-network-client-id',
      },
      networksMetadata: {
        'test-network-client-id': {
          EIPS: {},
          status: NetworkStatus.Unknown,
        },
      },
      selectedNetworkClientId: 'test-network-client-id',
    });
  });

  it('should add a selectedNetworkClientId property and a default networksMetadata object to the NetworkController state if there is a providerConfig.type value in state and it is not NetworkType.rpc (and there are no networkStatus or networkDetails values)', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          ticker: 'sepoliaETH',
          type: NetworkType.sepolia,
          chainId: '0xaa36a7',
          nickname: 'Sepolia TestNet',
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data.NetworkController).toStrictEqual({
      providerConfig: {
        ticker: 'sepoliaETH',
        type: NetworkType.sepolia,
        chainId: '0xaa36a7',
        nickname: 'Sepolia TestNet',
      },
      networksMetadata: {
        [NetworkType.sepolia]: {
          EIPS: {},
          status: NetworkStatus.Unknown,
        },
      },
      selectedNetworkClientId: NetworkType.sepolia,
    });
  });

  it('should migrate networkStatus into networksMetadata keyed by the active providerConfig.id value, calling the new value "status" and deleting the root networkStatus value', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.rpc,
          chainId: '0x9393',
          nickname: 'Funky Town Chain',
          ticker: 'ETH',
          id: 'test-network-client-id',
        },
        networkStatus: NetworkStatus.Available,
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual({
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.rpc,
          chainId: '0x9393',
          nickname: 'Funky Town Chain',
          ticker: 'ETH',
          id: 'test-network-client-id',
        },
        networksMetadata: {
          'test-network-client-id': {
            status: NetworkStatus.Available,
            EIPS: {},
          },
        },
        selectedNetworkClientId: 'test-network-client-id',
      },
    });
  });

  it('should migrate networkStatus into networksMetadata keyed by the active providerConfig.type value (if providerConfig.type is not NetworkType.rpc), calling the new value "status" and deleting the root networkStatus value', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.sepolia,
          ticker: 'sepoliaETH',
          chainId: '0xaa36a7',
          nickname: 'Sepolia TestNet',
        },
        networkStatus: NetworkStatus.Available,
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual({
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.sepolia,
          ticker: 'sepoliaETH',
          chainId: '0xaa36a7',
          nickname: 'Sepolia TestNet',
        },
        networksMetadata: {
          [NetworkType.sepolia]: {
            status: NetworkStatus.Available,
            EIPS: {},
          },
        },
        selectedNetworkClientId: NetworkType.sepolia,
      },
    });
  });

  it('should migrate the contents of networkDetails into networksMetadata keyed by the active providerConfig.id value, and delete the root networkDetails value', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.rpc,
          chainId: '0x9393',
          nickname: 'Funky Town Chain',
          ticker: 'ETH',
          id: 'test-network-client-id',
        },
        networkDetails: {
          EIPS: { 1559: false },
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual({
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.rpc,
          chainId: '0x9393',
          nickname: 'Funky Town Chain',
          ticker: 'ETH',
          id: 'test-network-client-id',
        },
        networksMetadata: {
          'test-network-client-id': {
            status: NetworkStatus.Unknown,
            EIPS: {
              1559: false,
            },
          },
        },
        selectedNetworkClientId: 'test-network-client-id',
      },
    });
  });

  it('should migrate the contents of networkDetails into networksMetadata keyed by the active providerConfig.type (if providerConfig.type does not equal NetworkType.rpc) value, and delete the root networkDetails value', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.sepolia,
          ticker: 'sepoliaETH',
          chainId: '0xaa36a7',
          nickname: 'Sepolia TestNet',
        },
        networkDetails: {
          EIPS: { 1559: false },
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual({
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.sepolia,
          ticker: 'sepoliaETH',
          chainId: '0xaa36a7',
          nickname: 'Sepolia TestNet',
        },
        networksMetadata: {
          [NetworkType.sepolia]: {
            status: NetworkStatus.Unknown,
            EIPS: { 1559: false },
          },
        },
        selectedNetworkClientId: NetworkType.sepolia,
      },
    });
  });

  it('should migrate both networkStatus and networkDetails state into networksMetadata keyed by the active providerConfig.id value', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.rpc,
          chainId: '0x9393',
          nickname: 'Funky Town Chain',
          ticker: 'ETH',
          id: 'test-network-client-id',
        },
        networkStatus: NetworkStatus.Available,
        networkDetails: {
          EIPS: { 1559: false },
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual({
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.rpc,
          chainId: '0x9393',
          nickname: 'Funky Town Chain',
          ticker: 'ETH',
          id: 'test-network-client-id',
        },
        networksMetadata: {
          'test-network-client-id': {
            status: NetworkStatus.Available,
            EIPS: { 1559: false },
          },
        },
        selectedNetworkClientId: 'test-network-client-id',
      },
    });
  });

  it('should migrate both networkStatus and networkDetails state into networksMetadata keyed by the active providerConfig.type (if providerConfig.type does not equal NetworkType.rpc)', async () => {
    const oldData = {
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.sepolia,
          ticker: 'sepoliaETH',
          chainId: '0xaa36a7',
          nickname: 'Sepolia TestNet',
        },
        networkStatus: NetworkStatus.Available,
        networkDetails: {
          EIPS: { 1559: false },
        },
      },
    };
    const oldStorage = {
      meta: {
        version: 93,
      },
      data: oldData,
    };

    const newStorage = await migrate(oldStorage);
    expect(newStorage.data).toStrictEqual({
      other: 'data',
      NetworkController: {
        providerConfig: {
          type: NetworkType.sepolia,
          ticker: 'sepoliaETH',
          chainId: '0xaa36a7',
          nickname: 'Sepolia TestNet',
        },
        networksMetadata: {
          [NetworkType.sepolia]: {
            status: NetworkStatus.Available,
            EIPS: { 1559: false },
          },
        },
        selectedNetworkClientId: NetworkType.sepolia,
      },
    });
  });
});