import { HttpService } from "@nestjs/axios";
import { GatewayTimeoutException, HttpException } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { Test } from "@nestjs/testing";
import { of } from "rxjs";
import { SearchQueryDto } from "src/core/domain";
import { SearchService } from "src/core/services/common/search.service";

describe('Unit tests for SearchService', () => {
    let searchService: SearchService;
    let httpService: HttpService;

    beforeAll(async () => {
        const moduleRef = await Test.createTestingModule({
            providers: [
                SearchService,
                {
                    provide: HttpService,
                    useValue: {
                        get: jest.fn(),
                    },
                },
            ],
            imports: [
                ConfigModule.forRoot({
                isGlobal: true,
                cache: true,
                expandVariables: true,
                })
            ],
        }).compile();

        searchService = moduleRef.get(SearchService);
        httpService = moduleRef.get(HttpService);
    });

    describe('findByID()', () => {
        it('Should touch HttpService.get() method', () => {
            let httpGetSpy = jest.spyOn(httpService, 'get');

            searchService.findByID('');
            expect(httpGetSpy).toHaveBeenCalled();
        });

        it('Should call HttpService.get() with correct URI and port number', () => {
            let httpGetSpy = jest.spyOn(httpService, 'get');
            
            searchService.findByID('');
            expect(httpGetSpy).toHaveBeenCalledWith<[string, object]>(
                `http://${process.env.ES_CONTAINER_NAME}:${process.env.ES_PORT}/_search`, 
                expect.anything()
            );
        });

        it('Should return a Promise', () => {
            expect(searchService.findByID('')).toBeInstanceOf(Promise);
        });

        // Errors
        it('Should throw 504 | GatewayTimeoutException', () => {
            // Axios response mock
            httpService.get = jest.fn().mockReturnValueOnce(
                of({
                    status: undefined,
                    statusText: undefined,
                    headers: undefined,
                    config: undefined,
                    data: {
                        timed_out: true,
                        dummy: 'dum'
                    }
                })
            );

            searchService.findByID('').catch((err) => {
                expect(err).toBeInstanceOf(GatewayTimeoutException);
            });
        });

        it('Should throw an HttpException when HttpService.get() fails and throws', () => {
            httpService.get = jest.fn().mockImplementationOnce(() => {
                throw new HttpException({ oops: 'sorry' }, 999);
            });

            searchService.findByID('').catch((err) => {
                expect(err).toBeInstanceOf(HttpException);
                expect(err.response).toEqual({ oops: 'sorry' });
                expect(err.status).toEqual(999);
            });
        });
    });

    describe('findByContext()', () => {
        it('Should touch HttpService.get() method', () => {
            let httpGetSpy = jest.spyOn(httpService, 'get');

            searchService.findByContext({query: ""});
            expect(httpGetSpy).toHaveBeenCalled();
        });

        it('Should send correct data via HttpService.get() body parameter', () => {
            let httpGetSpy = jest.spyOn(httpService, 'get');
            
            const query = new SearchQueryDto('keyword', 1, 32, 'desc');

            searchService.findByContext(query);
            expect(httpGetSpy).toHaveBeenCalledWith<[string, object]>(expect.anything(), {
                data: {
                    query: {
                        multi_match: {
                            query: query.query,
                            fields: [
                                'title',
                                'content'
                            ]
                        }
                    },
                    from: query.offset,
                    size: query.limit
                },
                headers: { 'Content-Type': 'application/json' }
            });
        });

        it('Should call HttpService.get() with correct URI and port number', () => {
            let httpGetSpy = jest.spyOn(httpService, 'get');
            
            searchService.findByContext({query: ""});
            expect(httpGetSpy).toHaveBeenCalledWith<[string, object]>(
                `http://${process.env.ES_CONTAINER_NAME}:${process.env.ES_PORT}/_search`, 
                expect.anything()
            );
        });

        it('Should return a Promise', () => {
            expect(searchService.findByContext({query: ""})).toBeInstanceOf(Promise);
        });

        // Errors
        it('Should throw 504 | GatewayTimeoutException', () => {
            // Axios response mock
            httpService.get = jest.fn().mockReturnValueOnce(
                of({
                    status: undefined,
                    statusText: undefined,
                    headers: undefined,
                    config: undefined,
                    data: {
                        timed_out: true,
                        dummy: 'dum'
                    }
                })
            );

            searchService.findByContext({query: ""}).catch((err) => {
                expect(err).toBeInstanceOf(GatewayTimeoutException);
            });
        });

        it('Should throw an HttpException when HttpService.get() fails and throws', () => {
            httpService.get = jest.fn().mockImplementationOnce(() => {
                throw new HttpException({ oops: 'sorry' }, 999);
            });

            searchService.findByContext({query: ""}).catch((err) => {
                expect(err).toBeInstanceOf(HttpException);
                expect(err.response).toEqual({ oops: 'sorry' });
                expect(err.status).toEqual(999);
            });
        });
    });
});