import { NextFunction, Request, Response } from "express"

export type Handle = (req: Request, res: Response, next: NextFunction) => void
export type Authorize = (privilege: string, action?: number) => Handle

export interface SimpleMap {
  [key: string]: string | number | boolean | Date
}
// tslint:disable-next-line:max-classes-per-file
export class Authorizer {
  protected userId: string
  protected permissions: string
  constructor(
    protected privilege: (userId: string, privilegeId: string) => Promise<number>,
    protected logError: (msg: string, m?: SimpleMap, ctx?: any) => void,
    protected exact?: boolean,
    userId?: string,
    permissions?: string,
  ) {
    this.userId = userId ? userId : "userId"
    this.permissions = permissions ? permissions : "permissions"
    this.exact = exact !== undefined ? exact : true
    this.authorize = this.authorize.bind(this)
  }
  authorize(privilege: string, action?: number): Handle {
    return (req: Request, res: Response, next: NextFunction) => {
      const userId = res.locals[this.userId]
      if (!userId) {
        res.status(401).end("Authentication is required or session has expired.")
      } else {
        this.privilege(userId, privilege)
          .then((p) => {
            if (p === none) {
              res.status(403).end("no permission for " + userId)
            } else {
              res.locals[this.permissions] = p
              if (!action) {
                next()
              } else {
                if (this.exact) {
                  // tslint:disable-next-line:no-bitwise
                  const sum = action & p
                  if (sum === action) {
                    return next()
                  } else {
                    res.status(403).end("no permission")
                  }
                } else {
                  if (p >= action) {
                    return next()
                  } else {
                    res.status(403).end("no permission")
                  }
                }
              }
            }
          })
          .catch((err) => {
            this.logError(`Error in privilege check for user ${userId} with privilege ${privilege}. Details: ${toString(err)}`)
            res.status(403).end("no permission")
          })
      }
    }
  }
}
export const none = 0
export const read = 1
export const write = 2
export const approve = 4
export const all = 2147483647
// tslint:disable-next-line:max-classes-per-file
export class PrivilegeLoader {
  constructor(
    public sql: string,
    public query: <T>(sql: string, args?: any[]) => Promise<T[]>,
  ) {
    this.privilege = this.privilege.bind(this)
  }
  privilege(userId: string, privilegeId: string): Promise<number> {
    return this.query<any>(this.sql, [userId, privilegeId]).then((v) => {
      if (!v || v.length === 0) {
        return none
      }
      const keys = Object.keys(v[0])
      if (keys.length === 0) {
        return all
      }
      const k: string = keys[0]
      let permissions = 0
      let ok = false
      for (const p of v) {
        const x = p[k]
        if (typeof x === "number") {
          // tslint:disable-next-line:no-bitwise
          permissions = permissions | x
          ok = true
        }
      }
      return ok ? permissions : all
    })
  }
}

export function toString(err: any): string {
  return typeof err === "string" ? err : JSON.stringify(err)
}
