import logger from "../logger"
import { WhereOptions } from 'sequelize'
import { Channel } from "../models/channels"
import { scheduleJob, Range, Job } from 'node-schedule'
import { Item } from "../models/items"
import { fn } from 'sequelize'
import { ChannelHandler } from "./ChannelHandler"
import { ExtChannelHandler } from "./ext/ExtChannelHandler"
import { WBChannelHandler } from "./wb/WBChannelHandler"
import { OzonChannelHandler } from "./ozon/OzonChannelHandler"
import { YMChannelHandler } from "./ym/YMChannelHandler"

export class ChannelsManager {
    private tenantId: string
    private jobMap: Record<string, [Job|null, boolean]> = {}

    public constructor(tenantId: string) { this.tenantId = tenantId }
    public getTenantId() { return this.tenantId }

    public addChannel(channel: Channel) {
        this.startChannel(channel)
    }

    public stopChannel(channel: Channel) {
        const tst = this.jobMap[channel.identifier]
        if (tst && tst[0]) tst[0].cancel()
    }

    public async triggerChannel(channel: Channel,language: string, data: any) {
        logger.info("Channel " + channel.identifier + " was triggered, tenant: " + this.tenantId)

        if (!language) {
            logger.error("Failed to find language for automatic start for channel " + channel.identifier + ", processing stopped, tenant: " + this.tenantId)
            return
        }

        let jobDetails = this.jobMap[channel.identifier+(data?'_sync':'')]
        if (jobDetails) {
            if (jobDetails[1]) {
                logger.warn("Channel " + channel.identifier + " is already running, skip it, tenant: " + this.tenantId)
                return
            }
            jobDetails[1] = true
        } else {
            jobDetails = [null, true]
            this.jobMap[channel.identifier+(data?'_sync':'')] = jobDetails
        }

        if (!data) {
            try {
                const whereExpression: any = { tenantId: this.tenantId, channels: {} }
                whereExpression.channels[channel.identifier] = { status: 1 }
                const result: any = await Item.findAll({
                    attributes: [
                        [fn('count', '*'), 'count']
                    ],
                    where: whereExpression
                })
                const count = result[0].getDataValue('count')
                const handler = this.getHandler(channel)
                if (count > 0) {
                    logger.info("Found " + count + " submitted items for channel " + channel.identifier + ", tenant: " + this.tenantId)
                    await handler.processChannel(channel, language, data)
                } else {
                    logger.info("Submitted items are not found for channel " + channel.identifier + ", skiping it, tenant: " + this.tenantId)
                }
            } finally {
                jobDetails[1] = false
            }
        } else {
            try {
                const handler = this.getHandler(channel)
                await handler.processChannel(channel, language, data)
            } finally {
                jobDetails[1] = false
            }
        }
    }

    public startChannel(channel: Channel) {
        if (channel.active) {
            this.stopChannel(channel)
            if (!channel.config.start || channel.config.start === 1) {
                this.jobMap[channel.identifier] = [null, false]
            } else if (channel.config.start === 2) { //interval
                if(channel.config.interval) {
                    const range = new Range(0, 60, parseInt(channel.config.interval))
                    const job = scheduleJob({minute: range}, () => {
                        this.triggerChannel(channel, channel.config.language, null)
                    })  
                    this.jobMap[channel.identifier] = [job, false]
                } else {
                    logger.warn('Interval is not set for channel: ' + channel.identifier + ', tenant: ' + this.tenantId)
                }
            } else { // time
                if(channel.config.time) {
                    const arr = channel.config.time.split(':')
                    const job = scheduleJob({hour: parseInt(arr[0]), minute: parseInt(arr[1])}, () => {
                        this.triggerChannel(channel, channel.config.language, null)
                    })  
                    this.jobMap[channel.identifier] = [job, false]
                } else {
                    logger.warn('Time is not set for channel: ' + channel.identifier + ', tenant: ' + this.tenantId)
                }
            }
            if (!channel.config.syncStart || channel.config.syncStart === 1) {
                this.jobMap[channel.identifier+'_sync'] = [null, false]
            } else if (channel.config.syncStart === 2) { // sync interval
                if(channel.config.syncInterval) {
                    const range = new Range(0, 60, parseInt(channel.config.syncInterval))
                    const job = scheduleJob({minute: range}, () => {
                        this.triggerChannel(channel, channel.config.language, {sync:true})
                    })  
                    this.jobMap[channel.identifier+'_sync'] = [job, false]
                }
            } else { // sync time
                if(channel.config.syncTime) {
                    const arr = channel.config.syncTime.split(':')
                    const job = scheduleJob({hour: parseInt(arr[0]), minute: parseInt(arr[1])}, () => {
                        this.triggerChannel(channel, channel.config.language, {sync:true})
                    })  
                    this.jobMap[channel.identifier+'_sync'] = [job, false]
                }
            }
        }
    }

    private extChannelHandler = new ExtChannelHandler()
    private wbChannelHandler = new WBChannelHandler()
    private ozonChannelHandler = new OzonChannelHandler()
    private ymChannelHandler = new YMChannelHandler()
    public getHandler(channel: Channel): ChannelHandler {
        if (channel.type === 1) return this.extChannelHandler
        if (channel.type === 2) return this.wbChannelHandler
        if (channel.type === 3) return this.ozonChannelHandler
        if (channel.type === 4) return this.ymChannelHandler
        throw new Error('Failed to find handler for channel type: ' + channel.type)
    }
}

export class ChannelsManagerFactory {
    private static instance: ChannelsManagerFactory = new ChannelsManagerFactory()
    private tenantMap: Record<string, ChannelsManager> = {}
    
    private constructor() { }

    public static getInstance(): ChannelsManagerFactory {
        return ChannelsManagerFactory.instance
    }

    public getChannelsManager(tenant: string): ChannelsManager {
        let tst = this.tenantMap[tenant]
        if (!tst) {
            logger.warn('Can not find channels manager for tenant: ' + tenant);
            tst = new ChannelsManager(tenant)
            this.tenantMap[tenant] = tst
        }
        return tst
    }

    public async init() {
        let where: WhereOptions | undefined = undefined
        if (process.argv.length > 3) {
            where = {tenantId: process.argv.splice(3)}
        }
        await this.initChannels(where)
    }

    public async initChannels(where: WhereOptions | undefined) {
        const channels = await Channel.findAll({
            where: where,
            order: [['tenantId', 'DESC']]})
        if (!channels) return

        let mng: ChannelsManager | null = null
        for (var i = 0; i < channels.length; i++) {
            const channel = channels[i];
            if (!mng || mng.getTenantId() !== channel.tenantId) {
                mng = new ChannelsManager(channel.tenantId)
                this.tenantMap[channel.tenantId] = mng
            }
            mng.addChannel(channel)
        }
    }
}