import {provider} from "@/blockchain/provider.js";
import {TransactionState, TransactionType} from "@/blockchain/transactionDecl.js";
import {sleep, uuid} from "@/misc.js";
import {vaultContract} from "@/blockchain/contract.js";
import {switchChain, useWalletStore} from "@/blockchain/wallet.js";
import {toRaw} from "vue";
import {useChartOrderStore} from "@/orderbuild.js";
import {placementFee} from "@/fees.js";
import {router} from "@/router/router.js";


export class Transaction {
    constructor(chainId, type) {
        this.id = uuid()
        this.type = type
        this.state = TransactionState.Created
        this.tx = null
        this.chainId = chainId
        this.owner = null
        this.vault = null
        this.error = null
    }

    submit() {
        console.log('submitting transaction', this.type)
        const ws = useWalletStore();
        if ( ws.transaction !== null ) {
            console.error('Transaction already in progress', ws.transaction)
            return
        }
        ws.transaction = this
    }

    // "propose" means attach the transaction to a specific vault
    propose(owner, vault) {
        console.log('transaction bind', owner, vault)
        if (this.vault !== null && this.vault !== vault) {
            this.failed('proposed vault did not match withdrawl vault', vault, this.vault)
            return
        }
        this.owner = owner
        this.vault = vault
        this.send().catch(this.catchSend.bind(this))
        this.state = TransactionState.Proposed
    }

    async createTx(vaultContract) {
        throw Error('unimplemented')
    }

    signed(tx) {
        this.tx = tx
        this.state = TransactionState.Signed
    }

    rejected() {
        this.tx = null
        this.chainId = null
        this.owner = null
        this.vault = null
        this.end(TransactionState.Rejected)
        console.log('transaction rejected', this.id)
    }

    failed(e) {
        this.error = e
        this.end(TransactionState.Error)
        console.log('transaction failed', this.id, e)
    }

    mined(receipt) {
        this.receipt = receipt
        this.end(TransactionState.Mined)
        console.log('mined transaction', this.id, receipt)
    }

    isOpen() {
        return this.state >= TransactionState.Rejected
    }

    isClosed() {
        return this.state < TransactionState.Rejected
    }


    end(state) {
        this.state = state
        useWalletStore().transaction = null
    }


    async send() {
        console.log('sendTransaction', this)
        try {
            await switchChain(this.chainId)
        } catch (e) {
            if (e.code === 4001) {
                this.rejected()
                return null
            } else {
                this.failed(e)
                return null
            }
        }
        let signer
        try {
            signer = await provider.getSigner();
        } catch (e) {
            // {
            //     "code": -32002,
            //     "message": "Already processing eth_requestAccounts. Please wait."
            // }
            this.rejected()
            return null
        }
        let contract
        try {
            contract = await vaultContract(this.vault, signer)
        } catch (e) {
            this.failed('vault contract was null while sending order transaction')
            return null
        }
        try {
            const tx = toRaw(await this.createTx(contract))
            this.signed(tx)
            tx.wait().then(this.mined.bind(this)).catch(this.failed.bind(this))
            console.log(`sent transaction`, tx)
        }
        catch (e) {
            this.failed(e)
            return null
        }
        return this.tx
    }


    catchSend(e) {
        this.error = e
        if (e.info?.error?.code === 4001) {
            console.log(`wallet refused transaction`, this.id)
            this.rejected()
        } else {
            this.failed(e)
        }
    }

}


export class PlaceOrderTransaction extends Transaction {
    constructor(chainId, order) {
        super(chainId, TransactionType.PlaceOrder)
        this.order = order
        this.placementTime = Date.now()/1000
        this.fee = null  // dexorder place and gas fee total
    }


    async createTx(vaultContract) {
        const tries = 65;
        let i;
        let success = false
        for (i=0; !success && i<tries; i++ ) {
            try {
                console.error('getting placement fee', vaultContract, this.order)
                this.fee = await placementFee(vaultContract, this.order)
                success = true
            }
            catch (e) {
                console.warn('failed to get placement fee', e)
                await sleep(1000)
            }
        }
        if (!success)
            throw Error('failed to get placement fee')
        console.log('placing order', this.id, this.fee, this.order)
        return await vaultContract.placeDexorder(this.order, {value: this.fee.reduce((a, b) => a + b)})
    }


    end(state) {
        super.end(state)
        if (state === TransactionState.Mined) {
            useChartOrderStore().resetOrders()
            // noinspection JSIgnoredPromiseFromCall
            router.push({name: 'Status'})
        }

    }
}


export class CancelOrderTransaction extends Transaction {
    constructor(chainId, index) {
        super(chainId, TransactionType.CancelOrder)
        this.index = index
    }


    async createTx(vaultContract) {
        return await vaultContract.cancelDexorder(this.index)
    }
}


export class CancelAllTransaction extends Transaction {
    constructor(chainId, vault) {
        super(chainId, TransactionType.CancelAll)
        this.vault = vault
    }


    async createTx(vaultContract) {
        return await vaultContract.cancelAllDexorders()
    }
}


export class WithdrawTransaction extends Transaction {
    constructor(chainId, vault, token, amount) {
        super(chainId, TransactionType.Withdraw)
        this.token = token
        this.amount = amount
        this.vault = vault
    }


    async createTx(vaultContract) {
        return await vaultContract['withdraw(address,uint256)'](this.token.a, this.amount)
    }
}


export class WithdrawNativeTransaction extends Transaction {
    constructor(chainId, vault, amount) {
        super(chainId, TransactionType.WithdrawNative)
        this.amount = amount
        this.vault = vault
    }


    async createTx(vaultContract) {
        return await vaultContract['withdraw(uint256)'](this.amount)
    }
}


export class WrapTransaction extends Transaction {
    constructor(chainId, vault, amount) {
        super(chainId, TransactionType.Wrap)
        this.vault = vault
        this.amount = amount
    }

    async createTx(vaultContract) {
        return await vaultContract.wrap(this.amount)
    }
}


export class UnwrapTransaction extends Transaction {
    constructor(chainId, vault, amount) {
        super(chainId, TransactionType.Unwrap)
        this.amount = amount
    }

    async createTx(vaultContract) {
        return await vaultContract.unwrap(this.amount)
    }
}
