// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

import { flushPromises } from '@vue/test-utils'

import mockAxios from '../../../mock/mockAxios'
import common from '../../../common'
import mockData from '../../../mockData/MigrateWizard.mock'
import MigrateWizard from '@/views/compute/MigrateWizard'

jest.mock('axios', () => mockAxios)

let i18n
let store
let mocks
let router
let wrapper
const originalFunc = {}

router = common.createMockRouter(mockData.routes)
i18n = common.createMockI18n('en', mockData.messages)
store = common.createMockStore()
mocks = {
  $message: {
    error: jest.fn((message) => {})
  },
  $notification: {
    error: jest.fn((option) => {
      return option
    }),
    info: jest.fn((option) => {
      return option
    }),
    success: jest.fn((option) => {
      return option
    }),
    warning: jest.fn((option) => {
      return option
    })
  },
  $pollJob: jest.fn((obj) => {
    switch (obj.jobId) {
      case 'test-job-id-case-1':
        if ('successMethod' in obj) {
          obj.successMethod()
        }
        break
      case 'test-job-id-case-2':
        if ('errorMethod' in obj) {
          obj.errorMethod()
        }
        break
      case 'test-job-id-case-3':
        if ('catchMethod' in obj) {
          obj.catchMethod()
        }
        break
    }
  })
}

const factory = (opts = {}) => {
  router = opts.router || router
  i18n = opts.i18n || i18n
  store = opts.store || store
  mocks = opts.mocks || mocks

  return common.createFactory(MigrateWizard, {
    router,
    i18n,
    store,
    mocks,
    props: opts.props || {},
    data: opts.data || {}
  })
}

router.push('/')
describe('Views > compute > MigrateWizard.vue', () => {
  beforeEach(() => {
    jest.clearAllMocks()

    if (!wrapper) {
      mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
      wrapper = factory({
        props: { resource: {} },
        data: { columns: [] }
      })
    }

    if (i18n.global.locale !== 'en') i18n.global.locale = 'en'
  })

  afterEach(() => {
    if (wrapper) {
      wrapper.vm.columns = []
      wrapper.vm.searchQuery = ''
      wrapper.vm.page = 1
      wrapper.vm.pageSize = 10
      wrapper.vm.selectedHost = {}
    }

    if (Object.keys(originalFunc).length > 0) {
      Object.keys(originalFunc).forEach(key => {
        switch (key) {
          case 'fetchData':
            wrapper.vm.fetchData = originalFunc[key]
            break
          default:
            break
        }
      })
    }
  })

  describe('Methods', () => {
    describe('fetchData()', () => {
      it('API should be called with resource is empty and searchQuery is empty', async (done) => {
        await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
        await wrapper.setProps({ resource: {} })
        await wrapper.setData({ columns: [] })
        await wrapper.vm.fetchData()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'findHostsForMigration',
            virtualmachineid: undefined,
            keyword: '',
            page: 1,
            pagesize: 10,
            response: 'json'
          }
        })
        done()
      })

      it('API should be called with resource.id is empty and searchQuery is empty', async (done) => {
        await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
        await wrapper.setProps({ resource: { id: null } })
        await wrapper.setData({ columns: [] })
        await wrapper.vm.fetchData()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'findHostsForMigration',
            virtualmachineid: null,
            keyword: '',
            page: 1,
            pagesize: 10,
            response: 'json'
          }
        })
        done()
      })

      it('API should be called with resource.id is not empty and searchQuery is empty', async (done) => {
        await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
        await wrapper.setProps({ resource: { id: 'test-id-value' } })
        await wrapper.setData({ columns: [] })
        await wrapper.vm.fetchData()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'findHostsForMigration',
            virtualmachineid: 'test-id-value',
            keyword: '',
            page: 1,
            pagesize: 10,
            response: 'json'
          }
        })
        done()
      })

      it('API should be called with resource.id is not empty and searchQuery is not empty', async (done) => {
        await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
        await wrapper.setProps({ resource: { id: 'test-id-value' } })
        await wrapper.setData({ searchQuery: 'test-query-value', columns: [] })
        await wrapper.vm.fetchData()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'findHostsForMigration',
            virtualmachineid: 'test-id-value',
            keyword: 'test-query-value',
            page: 1,
            pagesize: 10,
            response: 'json'
          }
        })
        done()
      })

      it('API should be called with params assign by resource, searchQuery, page, pageSize', async (done) => {
        await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
        await wrapper.setProps({ resource: { id: 'test-id-value' } })
        await wrapper.setData({
          searchQuery: 'test-query-value',
          page: 2,
          pageSize: 20,
          columns: []
        })
        await wrapper.vm.fetchData()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'findHostsForMigration',
            virtualmachineid: 'test-id-value',
            keyword: 'test-query-value',
            page: 1,
            pagesize: 20,
            response: 'json'
          }
        })
        done()
      })

      it('check hosts, totalCount when api is called with response result is empty', async (done) => {
        await mockAxios.mockResolvedValue({ findhostsformigrationresponse: { count: 0, host: [] } })
        await wrapper.setProps({ resource: {} })
        await wrapper.vm.fetchData()
        await flushPromises()

        expect(wrapper.vm.hosts).toEqual([])
        expect(wrapper.vm.totalCount).toEqual(0)
        done()
      })

      it('check hosts, totalCount when api is called with response result is empty', async (done) => {
        await mockAxios.mockResolvedValue({
          findhostsformigrationresponse: {
            count: 1,
            host: [{
              id: 'test-host-id',
              name: 'test-host-name',
              suitability: 'test-host-suitability',
              cpuused: 'test-host-cpuused',
              memused: 'test-host-memused',
              select: 'test-host-select'
            }]
          }
        })
        await wrapper.setProps({ resource: {} })
        await wrapper.vm.fetchData()
        await flushPromises()

        expect(wrapper.vm.hosts).toEqual([{
          id: 'test-host-id',
          name: 'test-host-name',
          suitability: 'test-host-suitability',
          cpuused: 'test-host-cpuused',
          memused: 'test-host-memused',
          select: 'test-host-select'
        }])
        expect(wrapper.vm.totalCount).toEqual(1)
        done()
      })

      it('check $message.error should be called when api is called with throw error', async (done) => {
        const mockError = 'Error: throw error message'

        await mockAxios.mockRejectedValue(mockError)
        await wrapper.setProps({ resource: {} })
        await wrapper.vm.fetchData()
        await flushPromises()
        await flushPromises()

        expect(mocks.$message.error).toHaveBeenCalled()
        expect(mocks.$message.error).toHaveBeenLastCalledWith(`${i18n.global.t('message.load.host.failed')}: ${mockError}`)
        done()
      })
    })

    describe('submitForm()', () => {
      it('API should be called with selectedHost.requiresStorageMotion is true', async (done) => {
        const mockData = {
          migratevirtualmachineresponse: {
            jobid: 'test-job-id'
          },
          queryasyncjobresultresponse: {
            jobstatus: 1,
            jobresult: {
              name: 'test-name-value'
            }
          }
        }

        await router.push({ name: 'testRouter1' })
        await mockAxios.mockResolvedValue(mockData)
        await wrapper.setProps({
          resource: {
            id: 'test-resource-id',
            name: 'test-resource-name'
          }
        })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: true,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'migrateVirtualMachineWithVolume',
            hostid: 'test-host-id',
            virtualmachineid: 'test-resource-id',
            response: 'json'
          }
        })
        done()
      })

      it('API should be called with selectedHost.requiresStorageMotion is false', async (done) => {
        const mockData = {
          migratevirtualmachineresponse: {
            jobid: 'test-job-id'
          },
          queryasyncjobresultresponse: {
            jobstatus: 1,
            jobresult: {
              name: 'test-name-value'
            }
          }
        }

        await router.push({ name: 'testRouter2' })
        await mockAxios.mockResolvedValue(mockData)
        await wrapper.setProps({
          resource: {
            id: 'test-resource-id',
            name: 'test-resource-name'
          }
        })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: false,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'migrateVirtualMachine',
            hostid: 'test-host-id',
            virtualmachineid: 'test-resource-id',
            response: 'json'
          }
        })
        done()
      })

      it('API should be called with $route.meta.name not equals `vm`', async (done) => {
        const mockData = {
          migratesystemvmresponse: {
            jobid: 'test-job-id'
          },
          queryasyncjobresultresponse: {
            jobstatus: 1,
            jobresult: {
              name: 'test-name-value'
            }
          }
        }

        await router.push({ name: 'testRouter3' })
        await mockAxios.mockResolvedValue(mockData)
        await wrapper.setProps({
          resource: {
            id: 'test-resource-id',
            name: 'test-resource-name'
          }
        })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: false,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mockAxios).toHaveBeenCalled()
        expect(mockAxios).toHaveBeenLastCalledWith({
          url: '/',
          method: 'GET',
          data: new URLSearchParams(),
          params: {
            command: 'migrateSystemVm',
            hostid: 'test-host-id',
            virtualmachineid: 'test-resource-id',
            response: 'json'
          }
        })
        done()
      })

      it('$pollJob have successMethod() should be called with requiresStorageMotion is true', async (done) => {
        const mockData = {
          migratevirtualmachinewithvolumeresponse: {
            jobid: 'test-job-id-case-1'
          },
          queryasyncjobresultresponse: {
            jobstatus: 1,
            jobresult: {
              name: 'test-name-value'
            }
          }
        }

        await router.push({ name: 'testRouter4' })
        await mockAxios.mockResolvedValue(mockData)
        await wrapper.setProps({
          resource: {
            id: 'test-resource-id',
            name: 'test-resource-name'
          }
        })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: true,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mocks.$pollJob).toHaveBeenCalled()
        expect(wrapper.emitted()['close-action'][0]).toEqual([])
        done()
      })

      it('$pollJob have successMethod() should be called with requiresStorageMotion is false', async (done) => {
        const mockData = {
          migratevirtualmachineresponse: {
            jobid: 'test-job-id-case-2'
          },
          queryasyncjobresultresponse: {
            jobstatus: 1,
            jobresult: {
              name: 'test-name-value'
            }
          }
        }

        await router.push({ name: 'testRouter5' })
        await mockAxios.mockResolvedValue(mockData)
        await wrapper.setProps({
          resource: {
            id: 'test-resource-id',
            name: 'test-resource-name'
          }
        })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: false,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mocks.$pollJob).toHaveBeenCalled()
        expect(wrapper.emitted()['close-action'][0]).toEqual([])
        done()
      })

      it('$pollJob have errorMethod() should be called', async (done) => {
        const mockData = {
          migratevirtualmachinewithvolumeresponse: {
            jobid: 'test-job-id-case-3'
          },
          queryasyncjobresultresponse: {
            jobstatus: 2,
            jobresult: {
              errortext: 'test-error-message'
            }
          }
        }

        await router.push({ name: 'testRouter6' })
        await mockAxios.mockResolvedValue(mockData)
        await wrapper.setProps({
          resource: {
            id: 'test-resource-id',
            name: 'test-resource-name'
          }
        })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: true,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mocks.$pollJob).toHaveBeenCalled()
        expect(wrapper.emitted()['close-action'][0]).toEqual([])
        done()
      })

      it('$pollJob have catchMethod() should be called', async (done) => {
        const mockData = {
          migratevirtualmachinewithvolumeresponse: {
            jobid: 'test-job-id-case-4'
          }
        }

        await mockAxios.mockResolvedValue(mockData)
        await wrapper.setProps({
          resource: {
            id: 'test-resource-id',
            name: 'test-resource-name'
          }
        })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: true,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mocks.$pollJob).toHaveBeenCalled()
        expect(wrapper.emitted()['close-action'][0]).toEqual([])
        done()
      })

      it('$message.error should be called when api is called with throw error', async (done) => {
        const mockError = { message: 'Error: throw error message' }

        await mockAxios.mockRejectedValue(mockError)
        await wrapper.setProps({ resource: {} })
        await wrapper.setData({
          selectedHost: {
            requiresStorageMotion: true,
            id: 'test-host-id',
            name: 'test-host-name'
          }
        })
        await wrapper.vm.submitForm()
        await flushPromises()

        expect(mocks.$notification.error).toHaveBeenCalled()
        expect(mocks.$notification.error).toHaveBeenCalledWith({
          message: i18n.global.t('message.request.failed'),
          description: 'Error: throw error message',
          duration: 0
        })
        done()
      })
    })

    describe('handleChangePage()', () => {
      it('check page, pageSize and fetchData() when handleChangePage() is called', async (done) => {
        originalFunc.fetchData = wrapper.vm.fetchData
        wrapper.vm.fetchData = jest.fn()

        const fetchData = jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
        await wrapper.setProps({ resource: {} })
        await wrapper.setData({
          page: 1,
          pageSize: 10
        })
        await wrapper.vm.handleChangePage(2, 20)
        await flushPromises()

        expect(wrapper.vm.page).toEqual(2)
        expect(wrapper.vm.pageSize).toEqual(20)
        expect(fetchData).toBeCalled()
        done()
      })
    })

    describe('handleChangePageSize()', () => {
      it('check page, pageSize and fetchData() when handleChangePageSize() is called', async (done) => {
        originalFunc.fetchData = wrapper.vm.fetchData
        wrapper.vm.fetchData = jest.fn()

        const fetchData = jest.spyOn(wrapper.vm, 'fetchData').mockImplementation(() => {})
        await wrapper.setProps({ resource: {} })
        await wrapper.setData({
          page: 1,
          pageSize: 10
        })
        await wrapper.vm.handleChangePage(2, 20)
        await flushPromises()

        expect(wrapper.vm.page).toEqual(2)
        expect(wrapper.vm.pageSize).toEqual(20)
        expect(fetchData).toBeCalled()
        done()
      })
    })
  })
})
