import {Accept, Application, Component, Configurable, Inject, Validator} from '@lakutata/core'
import cacheManager, {Cache} from 'cache-manager'
import os from 'os'
import path from 'path'
import {TCacheOptions} from './types/TCacheOptions'
import RedisStore from 'cache-manager-ioredis'
import FsStore from 'cache-manager-fs-hash'
import MemcachedStore from 'cache-manager-memcached-store'

export {IRedisCacheOptions} from './interfaces/IRedisCacheOptions'
export {IMemcachedCacheOptions} from './interfaces/IMemcachedCacheOptions'
export {IBaseCacheOptions} from './interfaces/IBaseCacheOptions'
export {IMemoryCacheOptions} from './interfaces/IMemoryCacheOptions'
export {IFileSystemCacheOptions} from './interfaces/IFileSystemCacheOptions'
export {TCacheOptions as CacheOptions} from './types/TCacheOptions'

export class CacherComponent extends Component {

    @Inject(Application)
    protected readonly app: Application

    @Configurable()
    protected readonly prefix: string

    @Configurable()
    protected readonly options?: TCacheOptions

    protected ttl: number = Infinity

    protected cache: Cache

    protected async initialize(): Promise<void> {
        if (!this.options) {
            //使用memory
            this.cache = cacheManager.caching({store: 'memory', ttl: this.ttl})
        } else {
            const cacheOptions: TCacheOptions = this.options
            switch (cacheOptions.type) {
                case 'memory': {
                    this.ttl = cacheOptions.ttl ? cacheOptions.ttl : this.ttl
                    this.cache = cacheManager.caching({
                        store: 'memory',
                        ttl: this.ttl
                    })
                }
                    break
                case 'fs': {
                    this.ttl = cacheOptions.ttl ? cacheOptions.ttl : this.ttl
                    this.cache = cacheManager.caching({
                        store: FsStore,
                        ttl: this.ttl,
                        options: {
                            path: cacheOptions.directory ? cacheOptions.directory : path.resolve(os.tmpdir(), './._LCC_CACHE_'),
                            ttl: this.ttl,
                            subdirs: cacheOptions.subdirectories === undefined ? false : cacheOptions.subdirectories,
                            zip: cacheOptions.compression === undefined ? false : cacheOptions.compression
                        }
                    })
                }
                    break
                case 'memcached': {
                    this.ttl = cacheOptions.ttl ? cacheOptions.ttl : this.ttl
                    const Memcache = require('memcache-plus')
                    this.cache = cacheManager.caching({
                        store: MemcachedStore,
                        driver: Memcache,
                        options: {
                            hosts: Array.isArray(cacheOptions.hosts) ? cacheOptions.hosts : [cacheOptions.hosts],
                            autodiscover: cacheOptions.autodiscover === undefined ? false : cacheOptions.autodiscover,
                            backoffLimit: cacheOptions.backoffLimit === undefined ? 10000 : cacheOptions.backoffLimit,
                            bufferBeforeError: cacheOptions.bufferBeforeError === undefined ? 1000 : cacheOptions.bufferBeforeError,
                            disabled: cacheOptions.disabled === undefined ? false : cacheOptions.disabled,
                            maxValueSize: cacheOptions.maxValueSize === undefined ? 1048576 : cacheOptions.maxValueSize,
                            queue: cacheOptions.queue === undefined ? true : cacheOptions.queue,
                            netTimeout: cacheOptions.netTimeout === undefined ? 500 : cacheOptions.netTimeout,
                            reconnect: cacheOptions.reconnect === undefined ? true : cacheOptions.reconnect,
                            onNetError: cacheOptions.onNetError === undefined ? (err) => {
                                //do nothing
                            } : cacheOptions.onNetError
                        },
                        ttl: this.ttl
                    })
                }
                    break
                case 'redis': {
                    this.ttl = cacheOptions.ttl ? cacheOptions.ttl : this.ttl
                    this.cache = cacheManager.caching({
                        store: RedisStore,
                        host: cacheOptions.host,
                        port: cacheOptions.port,
                        password: cacheOptions.password,
                        db: cacheOptions.db,
                        ttl: this.ttl
                    })
                }
                    break
                default: {
                    this.cache = cacheManager.caching({
                        store: 'memory',
                        ttl: this.ttl
                    })
                }
            }
        }
    }

    /**
     * 生成缓存键名
     * @param key
     * @protected
     */
    protected generateCacheKey(key: string): string {
        if (this.prefix) {
            return `${this.app.getID()}_${this.prefix.toString()}_${key}`
        } else {
            return `${this.app.getID()}_${key}`
        }
    }

    /**
     * 设置缓存
     * @param key
     * @param value
     */
    @Accept([Validator.String, Validator.Any], {strict: true})
    public async set(key: string, value: any): Promise<boolean> {
        return new Promise(resolve => {
            this.cache.set(this.generateCacheKey(key), value, {ttl: this.ttl}, error => {
                if (error) {
                    resolve(false)
                } else {
                    resolve(true)
                }
            })
        })
    }

    /**
     * 读取缓存
     * @param key
     */
    @Accept(Validator.String, {strict: true})
    public async get<T = any>(key: string): Promise<T | null> {
        return new Promise<T | null>(resolve => {
            this.cache.get(this.generateCacheKey(key), (error, result) => {
                if (error) {
                    resolve(null)
                } else {
                    if (result === undefined) {
                        resolve(null)
                    } else {
                        resolve(result as T)
                    }
                }
            })
        })
    }

    /**
     * 读取缓存（若缓存不存在则设置缓存）
     * @param key
     * @param value
     */
    public async getOrSet<T = any>(key: string, value: T): Promise<T> {
        let cachedValue: T | null = await this.get(key)
        if (cachedValue === null) {
            await this.set(key, value)
            cachedValue = value
        }
        return cachedValue
    }

    /**
     * 删除缓存
     * @param key
     */
    @Accept(Validator.String, {strict: true})
    public async del(key: string): Promise<boolean> {
        return new Promise(resolve => {
            this.cache.del(this.generateCacheKey(key), error => {
                if (error) {
                    resolve(false)
                } else {
                    resolve(true)
                }
            })
        })
    }

    /**
     * 重置
     */
    public async reset(): Promise<void> {
        return new Promise<void>(resolve => {
            this.cache.reset(() => {
                resolve()
            })
        })
    }
}
