/*
 * Copyright (c) 2022.
 * Author Peter Placzek (tada5hi)
 * For the full copyright and license information,
 * view the LICENSE file that was distributed with this source code.
 */

import {Redis} from "ioredis";

export interface StatusOptions<T> {
    recordType?: string,
    redisPrefix: string,
    redisSuffix?: string,
    redisGetKey?: (key: T) => string
}

export interface OnlineIdsResponse {
    data: Record<string, any>[],
    meta: {
        limit?: number,
        offset?: number
        total: number
    }
}

export class RecordStatus<T extends Record<string, any> | undefined | number | string> {
    constructor(public readonly redisDatabase : Redis, public readonly options:  StatusOptions<T>) {

    }

    public async setOffline(id: string | number, key?: T) : Promise<void> {
        await this.redisDatabase.zrem(this.getRedisKey(key), id);

        await this.dropMeta(id, key);
    }

    public async setOnline(id: string | number, key?: T, meta?: Record<string, any>) : Promise<void> {
        const seconds = (Date.now() / 1000).toFixed();

        await this.redisDatabase.zadd(this.getRedisKey(key), seconds, id);
        if(typeof meta !== 'undefined') {
            await this.setMeta(id, meta, key);
        }
    }

    async getOnlineCount(key?: T) {
        return await this.redisDatabase.zcard(this.getRedisKey(key));
    }

    public async getOnline(key?: T, offset?: number, limit?: number) : Promise<OnlineIdsResponse> {
        try {
            let args : (string | number)[] = [
                this.getRedisKey(key),
                '+inf',
                '-inf',
                'WITHSCORES'
            ];

            if(typeof offset !== 'undefined' || typeof limit !== 'undefined') {
                offset = offset ?? 0;
                limit = limit ?? 50;

                args.push(offset);
                args.push(limit);
            }

            // @ts-ignore
            const set = await this.redisDatabase.zrevrangebyscore(...args);

            let clients : Record<string, any>[] = [];

            for(let i=0; i<set.length; i=i+2) {
                const id: number | string = Number.isNaN(set[i]) ? set[i] : parseInt(set[i]);
                let client : Record<string, any> = {
                    id: id,
                    meta: {
                        firstSeen: parseInt(set[i+1]) * 1000
                    }
                };

                if(typeof this.options.recordType !== 'undefined') {
                    client.type = this.options.recordType;
                }

                clients.push(client);
            }

            return {
                data: clients,
                meta: {
                    limit,
                    offset,
                    total: await this.getOnlineCount(key)
            }};
        } catch (e) {
            console.log(e);
            return {
                data: [],
                meta: {
                    limit,
                    offset,
                    total: 0
                }
            };
        }
    }

    //--------------------------------------------------------------------

    public async setMeta(id: string | number, meta: Record<string,any>, key?: T) {
        const redisPath : string = this.getRedisMetaKey(id, key);
        for(let index in meta) {
            if(!meta.hasOwnProperty(index)) continue;
            await this.redisDatabase.hset(redisPath, index, meta[index]);
        }
    }

    public async setMetaProperty(id: string | number, metaKey: string, metaValue: any, key?: T) {
        const redisPath : string = this.getRedisMetaKey(id, key);
        await this.redisDatabase.hset(redisPath, metaKey, metaValue);
    }

    public async dropMeta(id: string | number, key?: T) {
        const redisPath : string = this.getRedisMetaKey(id, key);
        await this.redisDatabase.del(redisPath);
    }

    //--------------------------------------------------------------------

    public getRedisKey(key?: T) : string {
        let prefix : string = this.options.redisPrefix + '.status';

        if(typeof this.options.redisSuffix !== 'undefined') {
            prefix += '.' + this.options.redisSuffix;
        }

        if(typeof key === 'undefined') {
            return prefix;
        }

        if(typeof this.options.redisGetKey !== 'undefined') {
            return prefix + '.' + this.options.redisGetKey(key);
        }

        if(typeof key === 'string' || typeof key === 'number') {
            return prefix + ':' + key;
        }

        return prefix;
    }

    public getRedisMetaKey(id: string | number, key?: T) {
        const redisKey = this.getRedisKey(key);

        return redisKey + 'meta:' + id;
    }
}
