import path from 'path'

import {INestApplication} from '@nestjs/common'
import {Test} from '@nestjs/testing'
import request from 'supertest'

import {SubscriberPhonebookResponseDto} from './dto/subscriber-phonebook-response.dto'
import {SubscriberPhonebookModule} from './subscriber-phonebook.module'

import {AppModule} from '~/app.module'
import {AppService} from '~/app.service'
import {AuthService} from '~/auth/auth.service'
import {db} from '~/entities'
import {HttpExceptionFilter} from '~/helpers/http-exception.filter'
import {Operation as PatchOperation} from '~/helpers/patch.helper'
import {ResponseValidationInterceptor} from '~/interceptors/validate.interceptor'
import {ValidateInputPipe} from '~/pipes/validate.pipe'

type PhonebookPost = {
    name: string
    number: string
    subscriber_id: number
    shared: boolean
}

describe('Customer Phonebook', () => {
    let app: INestApplication
    let appService: AppService
    let authService: AuthService
    let authHeader: [string, string]
    let createdPhonebookIds: number[] = []
    const creds = {username: 'administrator', password: 'administrator'}

    beforeAll(async () => {
        const moduleRef = await Test.createTestingModule({
            imports: [SubscriberPhonebookModule, AppModule],
        })
            .compile()

        appService = moduleRef.get<AppService>(AppService)
        authService = moduleRef.get<AuthService>(AuthService)

        createdPhonebookIds = []

        app = moduleRef.createNestApplication()

        // TODO import other app configuration from bootstrap()
        // like interceptors, etc.
        app.useGlobalPipes(new ValidateInputPipe())
        app.useGlobalFilters(new HttpExceptionFilter())
        app.useGlobalInterceptors(new ResponseValidationInterceptor())

        await app.init()
    })

    afterAll(async () => {
        if (appService.db.isInitialized)
            await appService.db.destroy()
        await app.close()
    })

    it('should be defined', () => {
        expect(app).toBeDefined()
    })

    it('db connection', () => {
        expect(appService.isDbInitialised).toBe(true)
        expect(appService.isDbAvailable).toBe(true)
    })

    it('mock authService.compareBcryptPassword', async () => {
        jest.spyOn(authService, 'compareBcryptPassword').mockImplementation(async () => true)
        expect(await authService.compareBcryptPassword('123', '456')).toBe(true)
    })

    it('obtain auth token', async () => {
        const response = await request(app.getHttpServer())
            .post('/auth/jwt')
            .send(creds)
        expect(response.status).toEqual(201)
        expect(response.body['access_token']).toBeDefined()
        authHeader = ['Authorization', 'Bearer ' + response.body['access_token']]
    })

    describe('', () => { // main tests block
        describe('POST', () => {
            it('create phonebook bulk with purge_existing', async () => {
                // test this first so it doesnt affect other tests via purge
                const purgeTest = await db.billing.SubscriberPhonebook.create({
                    subscriber_id: 3,
                    name: 'test',
                    number: '123',
                }).save()
                const response = await request(app.getHttpServer())
                    .post('/resellers/phonebook?purge_existing=true')
                    .set(...authHeader)
                    .attach('file', path.resolve(__dirname, './fixtures/subscriber_phonebook_test.csv'))
                expect(response.status).toEqual(201)
                const cnt = await db.billing.SubscriberPhonebook.count()
                expect(cnt).toEqual(2)
                await db.billing.SubscriberPhonebook.delete({id: +response.body[0].id})
                await db.billing.SubscriberPhonebook.delete({id: +response.body[1].id})
                await db.billing.SubscriberPhonebook.delete({id: purgeTest.id})
            })
            it('create phonebook', async () => {
                const phonebook1: PhonebookPost = {
                    name: 'test_phonebook1',
                    number: '123',
                    subscriber_id: 1,
                    shared: false,
                }
                const response = await request(app.getHttpServer())
                    .post('/subscribers/phonebook')
                    .set(...authHeader)
                    .send(phonebook1)
                expect(response.status).toEqual(201)
                createdPhonebookIds.push(+response.body[0].id)
            })
            it('creates phonebook bulk from csv', async () => {
                const response = await request(app.getHttpServer())
                    .post('/subscribers/phonebook')
                    .set(...authHeader)
                    .attach('file', path.resolve(__dirname, './fixtures/subscriber_phonebook_test.csv'))
                expect(response.status).toEqual(201)
                createdPhonebookIds.push(+response.body[0].id)
                createdPhonebookIds.push(+response.body[1].id)
            })
            it('fails to create phonebook bulk because duplicate', async () => {
                const response = await request(app.getHttpServer())
                    .post('/subscribers/phonebook')
                    .set(...authHeader)
                    .attach('file', path.resolve(__dirname, './fixtures/subscriber_phonebook_test.csv'))
                expect(response.status).toEqual(422)
            })
        })

        describe('GET', () => {
            it('read created phonebook', async () => {
                const response = await request(app.getHttpServer())
                    .get(`/subscribers/phonebook/${createdPhonebookIds[0]}`)
                    .set(...authHeader)
                expect(response.status).toEqual(200)
                const phonebook: SubscriberPhonebookResponseDto = response.body
                expect(phonebook.name).toEqual('test_phonebook1')
                expect(phonebook.number).toEqual('123')
                expect(phonebook.subscriber_id).toEqual(1)
            })
            it('read created phonebook from csv', async () => {
                const response = await request(app.getHttpServer())
                    .get(`/subscribers/phonebook/${createdPhonebookIds[1]}`)
                    .set(...authHeader)
                expect(response.status).toEqual(200)
                const phonebook: SubscriberPhonebookResponseDto = response.body
                expect(phonebook.name).toEqual('SUB_PHONEBOOK_TEST1')
            })
            it('read non-existing phonebook', async () => {
                const response = await request(app.getHttpServer())
                    .get('/subscribers/phonebook/999911111122')
                    .set(...authHeader)
                expect(response.status).toEqual(404)
            })
        })

        describe('PUT', () => {
            it('update phonebook test_phonebook1 > test_phonebook_foo', async () => {
                const phonebookfoo: PhonebookPost = {
                    name: 'test_phonebook_foo',
                    number: '123',
                    subscriber_id: 1,
                    shared: true,
                }
                const response = await request(app.getHttpServer())
                    .put(`/subscribers/phonebook/${createdPhonebookIds[0]}`)
                    .set(...authHeader)
                    .send (phonebookfoo)
                expect(response.status).toEqual(200)
            })
            it('read updated phonebook', async () => {
                const response = await request(app.getHttpServer())
                    .get(`/subscribers/phonebook/${createdPhonebookIds[0]}`)
                    .set(...authHeader)
                expect(response.status).toEqual(200)
                const phonebookset: SubscriberPhonebookResponseDto = response.body
                expect(phonebookset.name).toEqual('test_phonebook_foo')
            })
            it('update non-existing phonebook', async () => {
                const phonebookfoo: PhonebookPost = {
                    name: 'test_phonebook_foo_bar',
                    number: '1234',
                    subscriber_id: 1,
                    shared: true,
                }
                const response = await request(app.getHttpServer())
                    .put('/subscribers/phonebook/999911111122')
                    .set(...authHeader)
                    .send (phonebookfoo)
                expect(response.status).toEqual(404)
            })
        })

        describe('PATCH', () => {
            const patch: PatchOperation[] = [
                {
                    op: 'replace',
                    path: '/name',
                    value: 'test_phonebook_foobar',
                },
                {
                    op: 'replace',
                    path: '/number',
                    value: '999',
                },
            ]
            it('adjust phonebook', async () => {
                const response = await request(app.getHttpServer())
                    .patch(`/subscribers/phonebook/${createdPhonebookIds[0]}`)
                    .set(...authHeader)
                    .send(patch)
                expect(response.status).toEqual(200)
            })
            it('read updated phonebook  5', async () => {
                const response = await request(app.getHttpServer())
                    .get(`/subscribers/phonebook/${createdPhonebookIds[0]}`)
                    .set(...authHeader)
                expect(response.status).toEqual(200)
                const phonebookset: SubscriberPhonebookResponseDto = response.body
                expect(phonebookset.name).toEqual('test_phonebook_foobar')
                expect(phonebookset.number).toEqual('999')
            })
            it('adjust non-existing phonebook', async () => {
                const response = await request(app.getHttpServer())
                    .patch('/subscribers/phonebook/999911111122')
                    .set(...authHeader)
                    .send(patch)
                expect(response.status).toEqual(404)
            })
        })
    })

    describe('phonebook DELETE', () => {
        it('delete created phonebook', async () => {
            for (const id of createdPhonebookIds) {
                const result = await request(app.getHttpServer())
                    .delete(`/subscribers/phonebook/${id}`)
                    .set(...authHeader)
                expect(result.status).toEqual(200)
            }
        })
    })
})
