import { Test, TestingModule } from "@nestjs/testing";
import { INestApplication } from "@nestjs/common";
import request from 'supertest'
import { AppModule } from "../../infrastructure/modules";
import { ConfigModule } from "@nestjs/config";
import { HttpService } from "@nestjs/axios";
import { of } from "rxjs";
import { Order } from "../../core/domain";

describe('E2E Testing of /papers', () => {
    let app: INestApplication;
    let httpService: HttpService;

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

        app = moduleRef.createNestApplication();
        httpService = moduleRef.get(HttpService)
        await app.init();
    });

    it('Should be defined', () => {
        expect(app).toBeDefined();
        expect(httpService).toBeDefined();
    });
    
    it('GET /papers/{uuid} | Should return one exact item', async () => {
        const axiosRes = of({
            status: 200,
            statusText: 'statText',
            headers: null,
            config: null,
            data: {
                took: 5,
                timed_out: false,
                _shards: {},
                hits: {
                    total: {},
                    hits: [
                        {
                            _source: {
                                id: 'thisIsIDofTheFirstObject',
                                title: 'thisIsTitle',
                                authors: ['A1', 'A2', 'A3'],
                                topic: 'thisIsTopic',
                                summary: 'thisIsSummary',
                                tags: ['T1', 'T2', 'T3'],
                                content: 'thisIsContent'
                            }
                        }
                    ]
                }
            }
        });
        
        let httpGetSpy = jest.spyOn(httpService, 'get').mockReturnValueOnce(axiosRes);

        const test = await request(app.getHttpServer())
            .get('/papers/2d3dc418-7778-abab-b33f-3d63aa25db41') // ??? Fetch a random object from DB
            .expect(200);

        // Expect HttpService.get() method to be touched
        expect(httpGetSpy).toHaveBeenCalled();
        expect(httpGetSpy).toHaveReturnedWith(axiosRes);
        
        // Checking received data
        expect(test.body).toBeDefined();
        expect(test.body.id).toBeDefined();
        expect(test.body.id).toBe('thisIsIDofTheFirstObject');

        expect(test.body.title).toBeDefined();
        expect(test.body.title).toBe('thisIsTitle');

        expect(test.body.authors).toBeDefined();
        expect(test.body.authors).toEqual(['A1', 'A2', 'A3']);

        expect(test.body.topic).toBeDefined();
        expect(test.body.topic).toBe('thisIsTopic');

        expect(test.body.summary).toBeDefined();
        expect(test.body.summary).toBe('thisIsSummary');

        expect(test.body.tags).toBeDefined();
        expect(test.body.tags).toEqual(['T1', 'T2', 'T3']);

        expect(test.body.content).toBeDefined();
        expect(test.body.content).toBe('thisIsContent');
    });

    it('GET /papers/search? | Should return multiple items on the page and correct meta', async () => {
        const axiosResGet = of({
            status: 200,
            statusText: 'statText',
            headers: null,
            config: null,
            data: {
                took: 5,
                timed_out: false,
                _shards: {},
                hits: {
                    total: {
                        value: 2,
                    },
                    hits: [
                        {
                            _source: {
                                id: 'thisIsIDofTheFirstObject',
                                title: 'thisIsTitle1',
                                authors: ['A1', 'A2', 'A3'],
                                topic: 'thisIsTopic1',
                                summary: 'thisIsSummary1',
                                tags: ['T1', 'T2', 'T3'],
                                content: 'thisIsContent1'
                            }
                        },
                        {
                            _source: {
                                id: 'thisIsIDofTheSecondObject',
                                title: 'thisIsTitle2',
                                authors: ['A4', 'A5', 'A6'],
                                topic: 'thisIsTopic2',
                                summary: 'thisIsSummary2',
                                tags: ['T11', 'T2', 'T8'],
                                content: 'thisIsContent2'
                            }
                        }
                    ]
                }
            }
        });

        let httpGetSpy = jest.spyOn(httpService, 'get').mockReturnValueOnce(axiosResGet);
        let httpPostSpy = jest.spyOn(httpService, 'post').mockReturnValueOnce(of({
            data: {},
            status: 200,
            statusText: 'statText',
            headers: null,
            config: null,
        }));

        const test = await request(app.getHttpServer())
            .get('/papers/search?query=at&page=1')
            .expect(200);

        // Expect HttpService.get() method to be touched
        expect(httpGetSpy).toHaveBeenCalled();
        expect(httpGetSpy).toHaveReturnedWith(axiosResGet);

        // Checking received data
        expect(test.body.data).toBeDefined();
        expect(test.body.data.length).toBe(2);

        for (const paper of test.body.data) {
            expect(paper.id).toBeDefined();
            expect(paper.title).toBeDefined();
            expect(paper.authors).toBeDefined();
            expect(paper.topic).toBeDefined();
            expect(paper.summary).toBeDefined();
            expect(paper.tags).toBeDefined();
            expect(paper.content).toBeDefined();
        }

        expect(test.body.data[0]).toEqual({
            id: 'thisIsIDofTheFirstObject',
            title: 'thisIsTitle1',
            authors: ['A1', 'A2', 'A3'],
            topic: 'thisIsTopic1',
            summary: 'thisIsSummary1',
            tags: ['T1', 'T2', 'T3'],
            content: 'thisIsContent1'
        });
        
        expect(test.body.data[1]).toEqual({
            id: 'thisIsIDofTheSecondObject',
            title: 'thisIsTitle2',
            authors: ['A4', 'A5', 'A6'],
            topic: 'thisIsTopic2',
            summary: 'thisIsSummary2',
            tags: ['T11', 'T2', 'T8'],
            content: 'thisIsContent2'
        });
        
        // // Checking received meta
        expect(test.body.meta).toBeDefined();

        expect(test.body.meta.total).toBeDefined();
        expect(test.body.meta.total).toBe(2);

        expect(test.body.meta.pagenum).toBeDefined();
        expect(test.body.meta.pagenum).toBe(1);

        expect(test.body.meta.order).toBeDefined();
        expect(test.body.meta.order).toBe(Order.DESC);
        
        expect(test.body.meta.pagesize).toBeDefined();
        expect(test.body.meta.pagesize).toBe(10);

        expect(test.body.meta.hasNext).toBeDefined();
        expect(test.body.meta.hasNext).toBe(false);

        expect(test.body.meta.hasPrev).toBeDefined();
        expect(test.body.meta.hasPrev).toBe(false);
    });

    afterAll(async () => {
        await app.close();
    })
});