import {newContract, vaultContract} from "@/blockchain/contract.js";
import {timestamp} from "@/common.js";
import TTLCache from "@isaacs/ttlcache";
import {provider} from "@/blockchain/provider.js";


async function getFeeManagerContract(vaultContract) {
    const feeManagerAddr = await vaultContract.feeManager()
    return await newContract(feeManagerAddr, 'IFeeManager', provider);
}


export async function getFeeSchedule(vaultAddr) {
    if (feeSchedCache.has(vaultAddr))
        return feeSchedCache.get(vaultAddr)
    const vault = await vaultContract(vaultAddr, provider)
    const feeManager = await getFeeManagerContract(vault);
    const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()])
    const changing = Number(changeTimestamp)
    const newSched = !changing ? null : await feeManager.proposedFees()
    // if it's not changing, we have an hour (wait 55 minutes) until another fee change could happen
    // otherwise, set the TTL to be a long TTL after the changeover
    const noticePeriod = 55*60
    const ttl = (!changing ? noticePeriod : (changing - timestamp() + noticePeriod))*1000 // milliseconds
    const schedule = new FeeSchedule(sched, newSched);
    feeSchedCache.set(vaultAddr, schedule, {ttl})
    return schedule
}


export async function placementFee(vaultContractOrAddr, order, window = 300) {
    // If the fees are about to change within `window` seconds of now, we send the higher native amount of the two fees.
    // If the fees sent are too much, the vault will refund the sender.
    const vault = typeof vaultContractOrAddr === 'string' ? await vaultContract(vaultContractOrAddr, provider) : vaultContractOrAddr
    const feeManager = await getFeeManagerContract(vault);
    const [sched, changeTimestamp] = await Promise.all([feeManager.fees(), feeManager.proposedFeeActivationTime()])
    console.log('sched', order, sched)
    // single order placement selector
    const placementFeeSelector = 'placementFee((address,address,(uint8,uint24),uint256,uint256,bool,bool,bool,uint64,(uint16,bool,bool,bool,bool,bool,bool,bool,bool,uint16,uint24,uint32,uint32,(uint32,uint32),(uint32,uint32))[]),(uint8,uint8,uint8,uint8,uint8))'
    let [orderFee, gasFee] = await vault[placementFeeSelector](order, [...sched])
    console.log('placementFee', orderFee, gasFee)
    if (Number(changeTimestamp) - timestamp() < window) {
        const nextSched = await feeManager.proposedFees()
        console.log('nextSched', new Date(Number(changeTimestamp)*1000), nextSched)
        const [nextOrderFee, nextGasFee] = await vault[placementFeeSelector](order, [...nextSched])
        if (nextOrderFee + nextGasFee > orderFee + gasFee)
            [orderFee, gasFee] = [nextOrderFee, nextGasFee]
    }
    return [orderFee, gasFee]
}


function schedToDict(sched) {
    if (sched===null)
        return null
    const [ofee, oexp, gfee, gexp, ffee] = sched
    console.log('sched', ofee, oexp, gfee, gexp, ffee)
    return {
        orderFee: BigInt(ofee) << BigInt(oexp), // in wei
        gasFee: BigInt(gfee) << BigInt(gexp),  // in wei
        fillFee: Number(ffee) / 200,  // float coefficient
    }
}


export class FeeSchedule {
    constructor(sched, nextSched=null) {
        // if nextSched is set, the more expensive of the two fees will be returned
        this.sched = schedToDict(sched)
        this.nextSched = schedToDict(nextSched)

        const _max = (method, ...args) => {
            const curVal = this[method](this.sched,...args);
            return this.nextSched === null ? curVal : Math.max(curVal, this[method](this.nextSched,...args))
        }

        this.gasFee = _max( '_gasFee')
        this.orderFee = _max('_orderFee')
        this.fillFee = _max('_fillFee')
    }

    _gasFee(sched) {return sched.gasFee}
    _orderFee(sched) {return sched.orderFee}
    _fillFee(sched) {return sched.fillFee}

}

const feeSchedCache = new TTLCache()
