import express, { Express, Request, Response, NextFunction } from 'express'
import { nanoid } from 'nanoid'
import * as availableConnectors from './connectors'
import { MemoryStoreAdapter, PersistentStoreAdapter } from './persistency'
import { BaseConnector, BaseHttpConnector, EventConfiguration } from 'reshuffle-base-connector'
import { createLogger } from './Logger'
import { Logger, LoggerOptions } from 'winston'
import http from 'http'

export interface Handler {
  handle: (event?: any) => void
  id: string
}

export default class Reshuffle {
  availableConnectors: any
  httpDelegates: { [path: string]: BaseHttpConnector }
  port: number
  registry: {
    connectors: { [url: string]: BaseConnector }
    handlers: { [id: string]: Handler[] }
    common: { webserver?: http.Server; persistentStore?: any }
  }
  logger: Logger

  constructor(loggerOptions?: LoggerOptions) {
    this.availableConnectors = availableConnectors
    this.port = parseInt(<string>process.env.PORT, 10) || 8000
    this.httpDelegates = {}
    this.registry = { connectors: {}, handlers: {}, common: {} }
    this.logger = createLogger(loggerOptions)

    this.logger.info('Initializing Reshuffle')
  }

  prepareWebServer(): Express {
    const server = express()
    server.route('*').all(async (req: Request, res: Response, next: NextFunction) => {
      let handled = false
      if (this.httpDelegates[req.params[0]]) {
        handled = await this.httpDelegates[req.params[0]].handle(req, res, next)
      }
      if (!handled) {
        res.end(`No handler registered for ${req.url}`)
      }
    })

    return server
  }

  register(connector: BaseConnector): Reshuffle {
    connector.app = this
    this.registry.connectors[connector.id] = connector

    return this
  }

  async unregister(connector: BaseConnector): Promise<void> {
    await connector.stop()
    delete this.registry.connectors[connector.id]
  }

  getConnector(connectorId: BaseConnector['id']): BaseConnector {
    return this.registry.connectors[connectorId]
  }

  registerHTTPDelegate(path: string, delegate: BaseHttpConnector): Reshuffle {
    this.httpDelegates[path] = delegate

    return this
  }

  unregisterHTTPDelegate(path: string): void {
    delete this.httpDelegates[path]
  }

  when(eventConfiguration: EventConfiguration, handler: () => void | Handler): Reshuffle {
    const handlerWrapper =
      typeof handler === 'object'
        ? handler
        : {
            handle: handler,
            id: nanoid(),
          }
    if (this.registry.handlers[eventConfiguration.id]) {
      this.registry.handlers[eventConfiguration.id].push(handlerWrapper)
    } else {
      this.registry.handlers[eventConfiguration.id] = [handlerWrapper]
    }
    this.logger.info('Registering event ' + eventConfiguration.id)

    return this
  }

  start(port?: number, callback?: () => void): void {
    this.port = port || this.port

    // Start all connectors
    Object.values(this.registry.connectors).forEach((connector) => connector.start(this))

    // Start the webserver if we have http delegates
    if (Object.keys(this.httpDelegates).length > 0 && !this.registry.common.webserver) {
      const express = this.prepareWebServer()

      this.registry.common.webserver = express.listen(this.port, () => {
        this.logger.info(`Web server listening on port ${this.port}`)
      })
    }

    callback && callback()
  }

  stopWebServer(): void {
    this.registry.common.webserver?.close()
  }

  restart(port?: number): void {
    this.start(port, () => {
      this.logger.info('Refreshing Reshuffle configuration')
    })
  }

  async handleEvent(eventId: string, event: any): Promise<boolean> {
    if (event == null) {
      event = {}
    }

    const eventHandlers = this.registry.handlers[eventId]
    if (eventHandlers.length === 0) {
      return false
    }

    event.getPersistentStore = this.getPersistentStore.bind(this)
    event.getConnector = this.getConnector.bind(this)

    for (const handler of eventHandlers) {
      await this._p_handle(handler, event)
    }

    return true
  }

  async _p_handle(handler: Handler, event: any): Promise<void> {
    this.logger.defaultMeta = { handlerId: handler.id }
    try {
      await handler.handle(event)
    } catch (error) {
      this.logger.error(error.stack)
    } finally {
      this.logger.defaultMeta = {}
    }
  }

  setPersistentStore(adapter: PersistentStoreAdapter) {
    this.registry.common.persistentStore = adapter
    return adapter
  }

  getPersistentStore() {
    return this.registry.common.persistentStore || this.setPersistentStore(new MemoryStoreAdapter())
  }

  public clearInterval(intervalID: NodeJS.Timer): void {
    global.clearInterval(intervalID)
  }

  public setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer {
    return global.setInterval(callback, ms, args)
  }

  public getLogger(): Logger {
    return this.logger
  }
}

export { Reshuffle }
