import {PrismaClient} from '@prisma/client'
import {logger} from '@wepublish/api'
import express, {Application, NextFunction} from 'express'
import set from 'lodash/set'
import {Provider} from 'oidc-provider'
import path from 'path'
import pino from 'pino'
import pinoHttp from 'pino-http'
import url from 'url'
import {MongoDBAdapter as OAuth2DBAdapter} from './adapter'
import {configuration} from './configuration'
import {routes} from './routes'

export interface OAuth2ServerOpts {
  readonly clientID: string
  readonly clientSecret: string
  readonly grantTypes: string[]
  readonly redirectUris: string[]
  readonly cookieKeys: string[]
  readonly jwksKeys: any

  readonly issuer: string

  readonly mongoUrlOauth2: string

  readonly viewPath?: string
  readonly debug?: boolean
  readonly logger?: pino.Logger
}

let serverLogger: pino.Logger

const ONE_DAY_IN_MS = 1 * 24 * 60 * 60 * 1000

export class Oauth2Server {
  private readonly app: Application
  private readonly opts: OAuth2ServerOpts

  private readonly prisma: PrismaClient

  constructor(opts: OAuth2ServerOpts) {
    const app = express()
    this.opts = opts
    this.prisma = new PrismaClient()
    this.prisma.$connect()

    serverLogger = opts.logger ? opts.logger : pino({name: 'oauth2'})

    /* const corsOptions = {
      origin: '*',
      allowedHeaders: [
        'authorization',
        'content-type',
        'content-length',
        'accept',
        'origin',
        'user-agent'
      ],
      methods: ['POST', 'GET', 'OPTIONS']
    } */

    app.use(
      pinoHttp({
        logger: serverLogger,
        useLevel: 'debug'
      })
    )

    app.use((err: any, req: Express.Request, res: Express.Response, next: NextFunction) => {
      logger('server').error(err)
      return next(err)
    })

    if (opts.viewPath) {
      app.set('views', opts.viewPath)
    } else {
      app.set('views', path.join(__dirname, '..', 'views'))
    }
    app.set('view engine', 'ejs')

    this.app = app
  }

  async findAccount(ctx: any, id: any) {
    const user = await this.prisma.user.findUnique({
      where: {id}
    })
    if (user) {
      return {
        accountId: user.id,
        email: user.email,
        async claims(use: any, scope: any) {
          console.log('claims', use, scope)
          return {sub: user.email, email: user.email}
        }
      }
    } else {
      throw new Error('did not find user')
    }
  }

  async listen(port?: number, hostname?: string): Promise<void> {
    await OAuth2DBAdapter.initialize(this.opts.mongoUrlOauth2, 'en')
    await OAuth2DBAdapter.connect(this.opts.mongoUrlOauth2)

    const config = {
      adapter: OAuth2DBAdapter,
      clients: [
        {
          client_id: this.opts.clientID,
          client_secret: this.opts.clientSecret,
          grant_types: this.opts.grantTypes,
          redirect_uris: this.opts.redirectUris
        }
      ],
      cookies: {
        long: {signed: true, maxAge: ONE_DAY_IN_MS},
        short: {signed: true},
        keys: this.opts.cookieKeys
      },
      jwks: {
        keys: this.opts.jwksKeys
      },
      findAccount: this.findAccount.bind(this),
      ...configuration
    }

    const provider = new Provider(this.opts.issuer, config)

    if (process.env.NODE_ENV === 'production') {
      this.app.enable('trust proxy')
      provider.proxy = true
      set(configuration, 'cookies.short.secure', true)
      set(configuration, 'cookies.long.secure', true)

      this.app.use((req, res, next) => {
        if (req.secure) {
          next()
        } else if (req.method === 'GET' || req.method === 'HEAD') {
          res.redirect(
            url.format({
              protocol: 'https',
              host: req.get('host'),
              pathname: req.originalUrl
            })
          )
        } else {
          res.status(400).json({
            error: 'invalid_request',
            error_description: 'do yourself a favor and only use https'
          })
        }
      })
    }

    routes(this.app, provider, this.prisma)
    this.app.use(provider.callback)
    console.log('views_path', path.join(__dirname, 'views'))
    this.app.listen(port ?? 4200, hostname ?? 'localhost')
  }
}
