import ALKS from 'alks.js';
import commander from 'commander';
import { checkForUpdate } from '../checkForUpdate';
import { errorAndExit } from '../errorAndExit';
import { getAlks } from '../getAlks';
import { getAuth } from '../getAuth';
import { getAwsAccountFromString } from '../getAwsAccountFromString';
import { handleAlksIamUpdateIamUser } from './alks-iam-updateiamuser';

jest.mock('../errorAndExit');
jest.mock('../checkForUpdate');
jest.mock('../getAlks');
jest.mock('../getAuth');
jest.mock('alks.js');
jest.mock('../getAwsAccountFromString');

// Silence console.error
jest.spyOn(global.console, 'error').mockImplementation(() => {});

// Silence console.log
jest.spyOn(global.console, 'log').mockImplementation(() => {});

describe('handleAlksIamUpdateIamUser', () => {
  interface TestCase {
    description: string;
    options: commander.OptionValues;
    shouldErr: boolean;
    checkForUpdateFails: boolean;
    shouldUpdateIamUser: boolean;
    updateIamUserParams: {
      account: string | undefined;
      iamUserName: string | undefined;
      tags: ALKS.Tag[] | undefined;
    };
    updateIamUserOutputParams: {
      accountId: string | undefined;
      accessKey: string | undefined;
      iamUserName: string | undefined;
      iamUserArn: string | undefined;
      tags: ALKS.Tag[] | undefined;
    };
    getAwsAccountFromString: typeof getAwsAccountFromString;
  }
  const defaultTestCase: Omit<TestCase, 'description'> = {
    options: {} as commander.OptionValues,
    shouldErr: false,
    checkForUpdateFails: false,
    shouldUpdateIamUser: true,
    updateIamUserParams: {
      account: '',
      iamUserName: '',
      tags: undefined,
    },
    updateIamUserOutputParams: {
      accountId: '',
      accessKey: '',
      iamUserName: '',
      iamUserArn: '',
      tags: undefined,
    },
    getAwsAccountFromString: async () => undefined,
  };
  const testCases: TestCase[] = [
    {
      ...defaultTestCase,
      description: 'when bad accountId is supplied',
      shouldErr: true,
      shouldUpdateIamUser: false,
      options: {
        account: 'badAccountId',
        iamusername: 'goodIamUserName',
        tags: [
          '{"Key":"key1", "Value":"val1"}',
          '{"Key":"key2", "Value":"val2"}',
        ],
      },
    },
    {
      ...defaultTestCase,
      description: 'when no tags nor empty list is provided',
      shouldErr: true,
      shouldUpdateIamUser: false,
      options: {
        account: '111111111111',
        iamusername: 'goodIamUserName',
      },
      getAwsAccountFromString: async () => ({
        id: '111111111111',
        alias: 'awsone',
        label: 'One - Prod',
      }),
    },
    {
      ...defaultTestCase,
      description: 'when empty list of tags is supplied',
      shouldErr: false,
      shouldUpdateIamUser: true,
      options: {
        account: '111111111111',
        iamusername: 'goodIamUserName',
        tags: [],
      },
      updateIamUserParams: {
        account: '111111111111',
        iamUserName: 'goodIamUserName',
        tags: [],
      },
      getAwsAccountFromString: async () => ({
        id: '111111111111',
        alias: 'awsone',
        label: 'One - Prod',
      }),
    },
    {
      ...defaultTestCase,
      description: 'when no username is supplied',
      shouldErr: true,
      shouldUpdateIamUser: false,
      options: {
        account: '111111111111',
        tags: [],
      },
      getAwsAccountFromString: async () => ({
        id: '111111111111',
        alias: 'awsone',
        label: 'One - Prod',
      }),
    },
    {
      ...defaultTestCase,
      description: 'When good data is supplied',
      shouldErr: false,
      shouldUpdateIamUser: true,
      options: {
        account: '111111111111',
        iamusername: 'goodIamUserName',
        tags: [
          '{"Key":"key1", "Value":"val1"}',
          '{"Key":"key2", "Value":"val2"}',
        ],
      },
      updateIamUserParams: {
        account: '111111111111',
        iamUserName: 'goodIamUserName',
        tags: [
          {
            key: 'key1',
            value: 'val1',
          },
          {
            key: 'key2',
            value: 'val2',
          },
        ],
      },
      getAwsAccountFromString: async () => ({
        id: '111111111111',
        alias: 'awsone',
        label: 'One - Prod',
      }),
    },
    {
      ...defaultTestCase,
      description: 'When account is supplied with accountID and roleName',
      shouldErr: false,
      shouldUpdateIamUser: true,
      options: {
        account: '111111111111/ALKSRole',
        iamusername: 'goodIamUserName',
        tags: [
          '{"Key":"key1", "Value":"val1"}',
          '{"Key":"key2", "Value":"val2"}',
        ],
      },
      updateIamUserParams: {
        account: '111111111111',
        iamUserName: 'goodIamUserName',
        tags: [
          {
            key: 'key1',
            value: 'val1',
          },
          {
            key: 'key2',
            value: 'val2',
          },
        ],
      },
      getAwsAccountFromString: async () => ({
        id: '111111111111',
        alias: 'awsone',
        label: 'One - Prod',
      }),
    },
  ];

  const fakeErrorSymbol = Symbol();

  const mockAlks = {
    updateIamUser: jest.fn(),
  } as unknown as ALKS.Alks;

  for (const t of testCases) {
    describe(t.description, () => {
      let errorThrown = false;

      beforeEach(async () => {
        (checkForUpdate as jest.Mock).mockImplementation(async () => {
          if (t.checkForUpdateFails) {
            throw new Error();
          }
        });
        (errorAndExit as unknown as jest.Mock).mockImplementation(() => {
          errorThrown = true;
          throw fakeErrorSymbol;
        });
        (getAuth as jest.Mock).mockImplementation(() => {
          return { accessToken: 'token' };
        });
        (getAlks as jest.Mock).mockImplementation(() => {
          return mockAlks;
        });
        (mockAlks.updateIamUser as jest.Mock).mockImplementation(() => {
          return t.updateIamUserOutputParams;
        });
        (getAwsAccountFromString as jest.Mock).mockImplementation(
          t.getAwsAccountFromString
        );

        try {
          await handleAlksIamUpdateIamUser(t.options);
        } catch (e) {
          if (!(e === fakeErrorSymbol)) {
            throw e;
          }
        }
      });

      if (t.shouldErr) {
        it('calls errorAndExit', () => {
          expect(errorThrown).toBe(true);
        });
      } else {
        it(`doesn't call errorAndExit`, () => {
          expect(errorThrown).toBe(false);
        });
      }

      if (t.shouldUpdateIamUser) {
        it('updates IAM User', () => {
          expect(mockAlks.updateIamUser).toHaveBeenCalledWith(
            t.updateIamUserParams
          );
        });
      } else {
        it('does not get key output', () => {
          expect(mockAlks.updateIamUser).not.toHaveBeenCalled();
        });
      }
    });
  }
});
