import cookieParser from 'cookie-parser'
import morgan from 'morgan'
import path from 'path'
import helmet from 'helmet'
import cors from 'cors'

import express, { NextFunction, Request, Response } from 'express'
import StatusCodes from 'http-status-codes'
import 'express-async-errors'

import BaseRouter from './routes'
import logger from '@shared/Logger'

import { MongoClient } from 'mongodb'
import { chain } from 'stream-chain'
import * as fs from 'fs'
import { parser } from 'stream-json'
import { streamArray } from 'stream-json/streamers/StreamArray'
import { IBikeTrip } from '@entities/BikeTrip'


/************************************************************************************
 *                              Fill database and open connection
 ***********************************************************************************/

export const dbName = 'bikesharing'
const url = `mongodb://localhost:27017/${dbName}`

export const dbClient = new MongoClient(url, { useNewUrlParser: true, useUnifiedTopology: true })

export const bikeTripsCollectionName = 'biketrips';

(async () => {
    try {
        console.log("Attempting to connect to MongoDB server.")
        await dbClient.connect()
        console.log("Connected to MongoDB server.")

        const db = dbClient.db(dbName)
        const bikeTripCollection = await db.collection(bikeTripsCollectionName)
        const stats = await bikeTripCollection.stats()

        if(stats.count === 0){
            // create ascending index on fields startDate and endDate
            bikeTripCollection.createIndex({ startDate : 1 })
            bikeTripCollection.createIndex({ endDate : 1 })

            const dataStreamFromFile = fs.createReadStream('src/shared/data/bike-sharing-trip-data-4-january-28-february-reduced.json')

            const pipeline = chain([
                dataStreamFromFile,
                parser(),
                streamArray(),
            ])

            let bikeTripsTemp: IBikeTrip[] = []

            const startTime = Date.now()
            console.log('Inserting bike trips into database, please wait...')

            pipeline.on('data', async bikeTripsChunk => {
                bikeTripsTemp.push(bikeTripsChunk.value)

                if(bikeTripsTemp.length === 50000){
                    dataStreamFromFile.pause()
                    await bikeTripCollection.insertMany(bikeTripsTemp)
                    bikeTripsTemp = []
                    console.log(`...${(await bikeTripCollection.stats()).count} documents in database...`)
                    dataStreamFromFile.resume()
                }
            })

            pipeline.on('end', async () => {
                await bikeTripCollection.insertMany(bikeTripsTemp)

                const bikeTripCollectionStats = await bikeTripCollection.stats()
                console.log(`Database inserts done! Added ${bikeTripCollectionStats.count} bike trip documents to database, in ${(Date.now() - startTime)/1000} seconds.`)
            })

        } else {
            console.log(`Found ${stats.count} bike trip documents in database`)
        }
    } catch (err) {
        console.log(err.stack)
    }
})()


/************************************************************************************
 *                              Set basic express settings
 ***********************************************************************************/

const app = express()
const { BAD_REQUEST } = StatusCodes

app.use(express.json())
app.use(express.urlencoded({extended: true}))
app.use(cookieParser())

// Show routes called in console during development
if (process.env.NODE_ENV === 'development') {
    app.use(morgan('dev'))
}

// Allow cors
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
app.use(cors())

// Security
if (process.env.NODE_ENV === 'production') {
    app.use(helmet())
}

// Add APIs
app.use('/api', BaseRouter)

// Print API errors
// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
    logger.err(err, true)
    return res.status(BAD_REQUEST).json({
        error: err.message,
    })
})


/************************************************************************************
 *                              Serve front-end content
 ***********************************************************************************/

const dir = path.join(__dirname, 'frontend-build')
app.set('views', dir)

// middleware is needed to make express serve static CSS and Javascript files
app.use(express.static(dir))

app.get('/*', (req: Request, res: Response) => {
    res.sendFile('index.html', {root: dir})
})

// Export express instance
export default app
