import {
  watchAndWaitForUploadAndScanComplete,
  checkIfVersionExists,
  handleResponse,
  handleError,
  isAxiosError,
  isAxiosResponse,
  isUserInOrganization,
} from './utils';
import axios, { AxiosResponse, AxiosError } from 'axios';

jest.mock('child_process', () => {
  return {
    execSync: (command: string) => {
      if (command === 'npm whoami') {
        return 'testuser\n';
      }
      if (command === 'npm org ls testuser foo') {
        return 'user role';
      }
      if (command === 'npm org ls testuser bar') {
        return 'No teams in the organization\n';
      }
      if (command === 'npm org ls testuser not_logged') {
        throw new Error('Not logged in');
      }
    },
  };
});

beforeEach(() => {
  jest.resetAllMocks();

  // Reset our process.env.NODE_ENV variable
  process.env.NODE_ENV = 'test';
});

const jobService = axios.create({
  baseURL: 'some_url',
  headers: {
    authorization: 'someToken',
  },
});

describe('cli lib utils watchAndWaitForUploadAndScanComplete', () => {
  it('should pass if virus scan is successful', async () => {
    jest
      .spyOn(jobService as any, `get`)
      .mockImplementation(() => Promise.resolve({ data: { status: 'Success' } } as AxiosResponse));

    const res = await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
    expect(res).toBeUndefined();
  });

  it('should throw specific error if virus scan is flagged', async () => {
    jest
      .spyOn(jobService as any, `get`)
      .mockImplementation(() => Promise.resolve({ data: { status: 'Flagged' } } as AxiosResponse));

    await expect(async () => {
      await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
    }).rejects.toThrow('upload has been flagged as a virus');
  });

  it('should throw generic error if virus scan is Error', async () => {
    jest
      .spyOn(jobService as any, `get`)
      .mockImplementation(() => Promise.resolve({ data: { status: 'Error' } } as AxiosResponse));

    await expect(async () => {
      await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
    }).rejects.toThrow('there has been an error');
  });

  it('should poll until a result is received', async () => {
    jest
      .spyOn(jobService as any, `get`)
      .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
      .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
      .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
      .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
      .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Success' } } as AxiosResponse));

    const res = await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
    expect(res).toBeUndefined();
    expect((jobService as any).get.mock.calls).toHaveLength(5);
  });
});

describe('cli lib utils checkIfVersionExists', () => {
  it('should return true if version exists', async () => {
    jest.spyOn(jobService as any, `get`).mockImplementation(() => Promise.resolve({ status: 200 } as AxiosResponse));

    const res = await checkIfVersionExists(jobService, '/job/testJob/1.0.0');
    expect(res).toBe(true);
  });

  it('should return false if version does not exist', async () => {
    jest.spyOn(jobService as any, `get`).mockImplementation(() => Promise.resolve({ status: 404 } as AxiosResponse));

    const res = await checkIfVersionExists(jobService, '/job/testJob/5.0.0');
    expect(res).toBe(false);
  });

  it('should throw an error if status is not 404 or 200', async () => {
    jest
      .spyOn(jobService as any, `get`)
      .mockImplementation(() =>
        Promise.resolve({ status: 500, data: { message: 'something bad happened' } } as AxiosResponse),
      );

    await expect(async () => {
      await checkIfVersionExists(jobService, '/job/testJob/5.0.0');
    }).rejects.toThrow('Unexpected response code 500. something bad happened');
  });
});

describe('cli lib utils handleResponse', () => {
  it('should return response data for a successful request', async () => {
    jest.spyOn(jobService as any, `get`).mockImplementation(
      () =>
        new Promise((resolve) => {
          resolve({ status: 200, data: { status: 'Successful' } } as AxiosResponse);
        }),
    );

    const response = jobService.get('/upload-job/status/123');
    const res = await handleResponse(response);
    expect(res).toStrictEqual({ status: 'Successful' });
  });

  it('should throw an error if error occurs in the request', async () => {
    jest.spyOn(jobService as any, `get`).mockImplementation(
      () =>
        new Promise((resolve, reject) => {
          reject(new Error('something really bad happened'));
        }),
    );

    const response = jobService.get('/upload-job/status/123');
    await expect(async () => {
      await handleResponse(response);
    }).rejects.toThrow('something really bad happened');
  });
});

describe('cli lib utils handleError', () => {
  it('should throw an error with message if not an AxiosError', async () => {
    const error = new Error('oh no');
    const res = handleError(error);

    expect(res.message).toBe('oh no');
  });
  it('should throw an error with generic message if not an AxiosError', async () => {
    const error = new Error();
    const res = handleError(error);

    expect(res.message).toBe('An error has occurred');
  });

  it('should throw an error with additional fields if an AxiosError without message', async () => {
    const error = new AxiosError('i am an axios error', '500', undefined, undefined, {
      status: 500,
      data: { status: 'fail' },
    } as AxiosResponse);
    const res = handleError(error);

    expect(res.message).toBe('i am an axios error (code: 500)');
  });

  it('should throw an error with additional fields if an AxiosError with message', async () => {
    const error = new AxiosError('i am an axios error', '500', undefined, undefined, {
      status: 500,
      data: { message: 'i have failed' },
    } as AxiosResponse);
    const res = handleError(error);

    expect(res.message).toBe('Unexpected response code 500. i have failed');
  });
});

describe('cli lib utils isAxiosError', () => {
  it('should return false if not an AxiosError', async () => {
    const error = new Error('oh no');
    const res = isAxiosError(error);

    expect(res).toBe(false);
  });
  it('should return true if  an AxiosError', async () => {
    const error = new AxiosError('i am an axios error', '500', undefined, undefined, {
      status: 500,
      data: { message: 'i have failed' },
    } as AxiosResponse);
    const res = isAxiosError(error);

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

describe('cli lib utils isAxiosResponse', () => {
  it('should return true if an AxiosResponse', async () => {
    const resp = {
      status: 200,
      data: { message: 'i have succeeded' },
    } as AxiosResponse;
    const res = isAxiosResponse(resp);

    expect(res).toBe(true);
  });
  it('should return false if not an AxiosResponse', async () => {
    const resp = {
      code: 200,
      data: { message: 'i am not an axios response' },
    };
    const res = isAxiosResponse(resp);

    expect(res).toBe(false);
  });
});

describe('isUserInFooOrganization', () => {
  it('should return true if the user is logged in and belongs to the "foo" organization', () => {
    const result = isUserInOrganization('foo');
    expect(result).toBe(true);
  });

  it('should return false if the user is logged in but does not belong to the "bar" organization', () => {
    const result = isUserInOrganization('bar');
    expect(result).toBe(false);
  });

  it('should return false if the user is not logged in', () => {
    const result = isUserInOrganization('not_logged');
    expect(result).toBe(false);
  });
});
