import { captureException } from '@sentry/node'
import { Redis } from 'ioredis'

import { PluginsServerConfig } from '../types'
import { status } from './status'
import { createRedis } from './utils'

export type PubSubTask = ((message: string) => void) | ((message: string) => Promise<void>)

export interface PubSubTaskMap {
    [channel: string]: PubSubTask
}

export class PubSub {
    private serverConfig: PluginsServerConfig
    private redis: Redis | null
    public taskMap: PubSubTaskMap

    constructor(serverConfig: PluginsServerConfig, taskMap: PubSubTaskMap = {}) {
        this.serverConfig = serverConfig
        this.redis = null
        this.taskMap = taskMap
    }

    public async start(): Promise<void> {
        if (this.redis) {
            throw new Error('Started PubSub cannot be started again!')
        }
        this.redis = await createRedis(this.serverConfig)
        const channels = Object.keys(this.taskMap)
        await this.redis.subscribe(channels)
        this.redis.on('message', (channel: string, message: string) => {
            const task: PubSubTask | undefined = this.taskMap[channel]
            if (!task) {
                captureException(
                    new Error(
                        `Received a pubsub message for unassociated channel ${channel}! Associated channels are: ${Object.keys(
                            this.taskMap
                        ).join(', ')}`
                    )
                )
            }
            void task(message)
        })
        status.info('👀', `Pub-sub started for channels: ${channels.join(', ')}`)
    }

    public async stop(): Promise<void> {
        if (!this.redis) {
            throw new Error('Unstarted PubSub cannot be stopped!')
        }
        await this.redis.unsubscribe()
        this.redis.disconnect()
        this.redis = null
        status.info('🛑', `Pub-sub stopped for channels: ${Object.keys(this.taskMap).join(', ')}`)
    }
}