import { CurrencyCode } from "../../commonTypes"
import CurrencyRepository from "./CurrencyRepository"
import *  as defaultConversions from "./usd.json"

let mockNetworkProxy

describe('Currency repository', () => {

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

    beforeEach(() => {
        jest.useRealTimers()
    })

    it('Should make a network request to get the conversion when it is not cached', async () => {
        const usd_rate = 1 / defaultConversions.usd[CurrencyCode.IDR.toLowerCase()]
        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2022-06-23', usd: usd_rate })
        }

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)

        expect(conversion).toEqual({ usd: usd_rate })
    })


    it('Should not make a new network request when the cached value has less than an hour', async () => {
        const usd_rate = 1 / defaultConversions.usd[CurrencyCode.IDR.toLowerCase()]
        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2022-06-23', usd: usd_rate })
        }
        const senderSpy = jest.spyOn(mockNetworkProxy, 'get')

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        jest
            .useFakeTimers()
            .setSystemTime(new Date('2020-01-10 10:30').getTime());

        await currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)
        jest.setSystemTime(new Date('2020-01-10 10:33').getTime());
        await currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)

        expect(senderSpy).toBeCalledTimes(1)
    })

    it('Should make a new network request when the cached value has more than an hour', async () => {
        const usd_rate = 1 / defaultConversions.usd[CurrencyCode.IDR.toLowerCase()]
        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2022-06-23', usd: usd_rate })
        }
        const senderSpy = jest.spyOn(mockNetworkProxy, 'get')

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        jest
            .useFakeTimers()
            .setSystemTime(new Date('2020-01-10 10:30').getTime());

        await currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)
        jest.setSystemTime(new Date('2020-01-10 11:33').getTime());
        await currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)

        expect(senderSpy).toBeCalledTimes(2)
        senderSpy.mockClear()
    })


    it('Should use a default value when then network request returns a non-number value', async () => {
        const USD_RATE_VALUE_FROM_JSON_FILE = 1 / defaultConversions.usd[CurrencyCode.EUR.toLowerCase()]

        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2020-01-10', usd: null })
        }

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)

        expect(conversion.usd).toEqual(USD_RATE_VALUE_FROM_JSON_FILE)
    })


    it('Should not make a network request when the source currency is USD', async () => {

        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2020-01-10', usd: 2 })
        }
        const senderSpy = jest.spyOn(mockNetworkProxy, 'get')

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        await currencyRepository.convertCurrencyToUSD(CurrencyCode.USD)

        expect(senderSpy).toBeCalledTimes(0)
    })

    it('Should return a hardcoded conversion when source currency is USD', async () => {

        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2020-01-10', usd: 2 })
        }
        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.USD)

        expect(conversion).toEqual({ usd: 1 })
    })

    it('Should reuse a cached value', async () => {

        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2020-01-10', usd: 2 })
        }
        const senderSpy = jest.spyOn(mockNetworkProxy, 'get')

        jest
            .useFakeTimers()
            .setSystemTime(new Date('2020-01-10').getTime());


        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)
        const conversion2 = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)
        const conversion3 = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)

        expect(senderSpy).toBeCalledTimes(1)
    })

    it('Should do not reuse a cached value when the day has changed', async () => {

        const mockNetworkProxy = {
            get: jest.fn().mockResolvedValue({ date: '2020-01-10', usd: 2 })
        }
        const senderSpy = jest.spyOn(mockNetworkProxy, 'get')

        jest
            .useFakeTimers()
            .setSystemTime(new Date('2020-01-10').getTime());

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)
        const conversion2 = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)
        const conversion3 = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)

        jest
            .useFakeTimers()
            .setSystemTime(new Date('2020-01-11').getTime());

        const conversion4 = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)

        expect(senderSpy).toBeCalledTimes(2)
    })

    it('Should return a default value when the network request fails', async () => {
        const USD_RATE_VALUE_FROM_JSON_FILE = 1 / defaultConversions.usd[CurrencyCode.IDR.toLowerCase()]

        let mockNetworkProxy = {
            get: jest.fn().mockRejectedValue(new Error('Async error'))
        }

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)

        expect(conversion.usd).toEqual(USD_RATE_VALUE_FROM_JSON_FILE)
    })

    it('Should wait for a previous request to return the converstion - to save network requests', async () => {
        const mockNetworkProxy2 = {
            get: jest.fn().mockImplementation(() => {
                return new Promise(resolve =>
                    setTimeout(
                        () => resolve({ date: '2020-01-10', usd: 2 })
                        , 100)
                )
            })
        }
        const senderSpy = jest.spyOn(mockNetworkProxy2, 'get')

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy2)

        currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)
        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)

        expect(senderSpy).toBeCalledTimes(1)
        expect(conversion.usd).toEqual(2)
    })


    it('Should resolved correctly a second request for a different currency, when are triggered concurrently', async () => {
        const mockNetworkProxy = {
            get: jest.fn()
                .mockResolvedValueOnce({ date: '2022-06-23', usd: 4 })
                .mockResolvedValueOnce({ date: '2022-06-23', usd: 5 })
        }

        const currencyRepository = new CurrencyRepository('/fakeurl', mockNetworkProxy)

        currencyRepository.convertCurrencyToUSD(CurrencyCode.IDR)
        const conversion = await currencyRepository.convertCurrencyToUSD(CurrencyCode.EUR)

        expect(conversion).toEqual({ usd: 5 })
    })


})