import EnhancedContentApi from '../index'
import request from '../../utils/request'
import { PerProductConfigCache, selectSource } from '../perProductConfig'
import { makeContext, makeSettings, makeResponse } from '../../__tests__/helpers'

jest.mock('../../utils/request')

const clientId = 'client-id'
const languageCode = 'lang-code'
const enhancedContent = {}

const productExistingPath = 'https://salsify-ecdn.com/sdk/client-id/lang-code/BTF/id-type/existing-product'
const productMissingPath = 'https://salsify-ecdn.com/sdk/client-id/lang-code/BTF/id-type/missing-product'

const exampleContent = '<div>enhanced-content</div>'
const emptyContent = ''

const PerProductConfigCacheMock = PerProductConfigCache as jest.Mock
const headMock = request.head as jest.Mock
const selectSourceMock = selectSource as jest.Mock

let ecApi: EnhancedContentApi
const log = jest.fn()
const settings = makeSettings({ clientId, languageCode, enhancedContent, tracking: true })
const settingsNoTrack = makeSettings({ clientId, languageCode, enhancedContent, tracking: false })

export default function commonSuite(runtime: string): void {
  describe(`EnhancedContentApi (${runtime})`, () => {
    beforeEach(() => {
      ecApi = new EnhancedContentApi(settings, makeContext(), { log })
      PerProductConfigCacheMock.mock.instances[0].getConfig.mockImplementation(() => undefined)
    })

    afterEach(() => {
      jest.clearAllMocks()
    })

    describe('exists', () => {
      test('should throw error when no product ID type is specified when calling', async () => {
        await expect(ecApi.exists('product')).rejects.toStrictEqual(new Error('No ID type specified.'))
      })

      test('should return true when content length is greater than zero', async () => {
        headMock.mockImplementation(() => makeResponse(exampleContent))
        const exists = await ecApi.exists('existing-product', 'id-type')
        expect(exists).toBe(true)
        expect(headMock).toBeCalledTimes(1)
        expect(headMock).toBeCalledWith(`${productExistingPath}/index.html`)
        expect(PerProductConfigCacheMock.mock.instances.length).toBe(1)
        expect(PerProductConfigCacheMock.mock.instances[0].getConfig).toHaveBeenCalledTimes(1)
        expect(PerProductConfigCacheMock.mock.instances[0].getConfig).toHaveBeenCalledWith(productExistingPath)
        const renderConfig = {
          idType: 'id-type',
          productId: 'existing-product',
          content: null,
          allContentExists: false,
          source: 'index.html',
          sourceExists: true,
        }
        expect(ecApi.lastRenderConfig).toMatchObject(renderConfig)
        expect(log).toHaveBeenCalledWith('ec_exists', renderConfig)
      })

      test('should return false when content length is zero', async () => {
        headMock.mockImplementation(() => makeResponse(emptyContent))
        const exists = await ecApi.exists('missing-product', 'id-type')
        expect(exists).toBe(false)
        expect(headMock).toBeCalledTimes(1)
        expect(headMock).toBeCalledWith(`${productMissingPath}/index.html`)
        expect(PerProductConfigCacheMock.mock.instances.length).toBe(1)
        expect(PerProductConfigCacheMock.mock.instances[0].getConfig).toHaveBeenCalledTimes(1)
        expect(PerProductConfigCacheMock.mock.instances[0].getConfig).toHaveBeenCalledWith(productMissingPath)
        const renderConfig = {
          idType: 'id-type',
          productId: 'missing-product',
          content: null,
          allContentExists: false,
          source: 'index.html',
          sourceExists: false,
        }
        expect(ecApi.lastRenderConfig).toMatchObject(renderConfig)
        expect(log).toHaveBeenCalledWith('ec_exists', renderConfig)
      })

      test('should cache results', async () => {
        headMock.mockImplementation(() => makeResponse(emptyContent))

        await ecApi.exists('new-product', 'id-type')
        await ecApi.exists('new-product', 'id-type')

        expect(headMock).toHaveBeenCalledTimes(1)
      })

      test('it should handle rejection', async () => {
        headMock.mockRejectedValue(Error('network error'))

        const exists = await ecApi.exists('rejected-product', 'id-type')

        expect(exists).toBe(false)
        expect(headMock).toHaveBeenCalledTimes(1)
        expect(log).toBeCalledWith('error', {
          errorContext: 'exists',
          errorType: 'fetch',
          errorMessage:
            'Error on HEAD request of https://salsify-ecdn.com/sdk/client-id/lang-code/BTF/id-type/rejected-product/index.html: network error',
        })
      })

      describe('with config content', () => {
        const content = [
          { source: 'foo.html', weight: 0.4 },
          { source: 'bar.html', weight: 0.4 },
          { source: null, weight: 0.2 },
        ]

        beforeEach(() => {
          PerProductConfigCacheMock.mock.instances[0].getConfig.mockImplementation(() => ({
            content,
          }))
        })

        test('it checks a content source from config', async () => {
          selectSourceMock.mockImplementation(() => content[1])
          headMock.mockImplementation(() => makeResponse(exampleContent))
          const exists = await ecApi.exists('existing-product', 'id-type')
          expect(exists).toBe(true)
          expect(headMock).toBeCalledTimes(2)
          expect(headMock).toBeCalledWith(`${productExistingPath}/foo.html`)
          expect(headMock).toBeCalledWith(`${productExistingPath}/bar.html`)
          const renderConfig = {
            idType: 'id-type',
            productId: 'existing-product',
            content,
            allContentExists: true,
            source: 'bar.html',
            sourceExists: true,
          }
          expect(ecApi.lastRenderConfig).toMatchObject(renderConfig)
          expect(log).toHaveBeenCalledWith('ec_exists', renderConfig)
        })

        test('it returns false when a null source is selected', async () => {
          selectSourceMock.mockImplementation(() => content[2])
          headMock.mockImplementation(() => makeResponse(exampleContent))
          const exists = await ecApi.exists('existing-product', 'id-type')
          expect(exists).toBe(false)
          expect(headMock).toBeCalledTimes(2)
          expect(headMock).toBeCalledWith(`${productExistingPath}/foo.html`)
          expect(headMock).toBeCalledWith(`${productExistingPath}/bar.html`)
          const renderConfig = {
            idType: 'id-type',
            productId: 'existing-product',
            content,
            allContentExists: true,
            source: null,
            sourceExists: false,
          }
          expect(ecApi.lastRenderConfig).toMatchObject(renderConfig)
          expect(log).toHaveBeenCalledWith('ec_exists', renderConfig)
        })

        describe('when tracking is disabled', () => {
          beforeEach(() => {
            ecApi = new EnhancedContentApi(settingsNoTrack, makeContext(), { log })
            PerProductConfigCacheMock.mock.instances[1].getConfig.mockImplementation(() => ({
              content,
            }))
          })

          test('it falls back to default source', async () => {
            selectSourceMock.mockImplementation(() => content[1])
            headMock.mockImplementation(() => makeResponse(exampleContent))
            const exists = await ecApi.exists('existing-product', 'id-type')
            expect(exists).toBe(true)
            expect(headMock).toBeCalledTimes(3)
            expect(headMock).toBeCalledWith(`${productExistingPath}/foo.html`)
            expect(headMock).toBeCalledWith(`${productExistingPath}/bar.html`)
            expect(headMock).toBeCalledWith(`${productExistingPath}/index.html`)
            expect(PerProductConfigCacheMock.mock.instances.length).toBe(2)
            expect(PerProductConfigCacheMock.mock.instances[1].getConfig).toHaveBeenCalledTimes(1)
            expect(PerProductConfigCacheMock.mock.instances[1].getConfig).toHaveBeenCalledWith(productExistingPath)
            const renderConfig = {
              idType: 'id-type',
              productId: 'existing-product',
              content,
              allContentExists: true,
              source: 'index.html',
              sourceExists: true,
            }
            expect(ecApi.lastRenderConfig).toMatchObject(renderConfig)
            expect(log).toHaveBeenCalledWith('ec_exists', renderConfig)
          })
        })
      })
    })
  })
}
