import { BigNumber, ethers } from "ethers";
import { StakedEvent, WithdrawnStakeEvent, WithdrawnStakeResult } from "../types";
import { EthereumContractService } from "../contract";
import { formatContractErrors } from "../utils";

export class StakeClient {

    private readonly contractService: EthereumContractService;
    
    get contract() { return this.contractService.contract; }

    constructor(contract: EthereumContractService) {
        this.contractService = contract;
    }
    


    /**
     * Simulate a stake via callStatic
     */
    async simulateStake(amountWei: BigNumber): Promise<void> {
        try {
            await this.contract.Depositor.callStatic.stakeLiqETH(amountWei);
        } catch (err: any) {
            let errorObj = formatContractErrors(err);
            throw new Error(errorObj.name ?? errorObj.raw)
        }
    }



    /**
     * 
     * @param amountWei an amount of liqETH (in WEI) to stake to the Outpost
     * @returns txHash (hash of the transaction), receipt, Staked event
     */
    async performStake(amountWei: BigNumber, signerAddress: string): Promise<any> {
        const depositor = this.contract.Depositor.address;
        const liqRead = this.contract.LiqEthToken;
        const bal = await liqRead.balanceOf(signerAddress);
        const allowance = await liqRead.allowance(signerAddress, depositor);

        const paused = await this.contract.Depositor.paused();
        if(paused) {
            throw new Error("Error - Depositor is in a paused state");
        }


        // if allowance is less than the requested stake amount, request permission to spend LiqEth
        if (allowance.lt(amountWei)) {
            const liqWrite = this.contractService.getWrite('LiqEthToken');

            console.warn(`allowance insufficient (${allowance.toString()} < ${amountWei.toString()}); sending approve(${depositor}, ${amountWei.toString()})`);
            const approveTx = await liqWrite.approve(depositor, amountWei);
            await approveTx.wait(1);

            // re-read allowance to ensure approval succeeded
            const newAllowance = await liqRead.allowance(signerAddress, depositor);
            if (newAllowance.lt(amountWei)) {
                throw new Error('Approval failed or allowance still insufficient after approve');
            }
        }
        
        
        await this.simulateStake(amountWei);


        // send the tx to stake liqeth
        const tx = await this.contract.Depositor.stakeLiqETH(amountWei);

        // Wait for 1 confirmation 
        const receipt = await tx.wait(1);

        // Parse Staked event if present
        let staked: StakedEvent | undefined;
        const ev = receipt.events?.find((e) => e.event === 'Staked');

        if (ev && ev.args) {
            const { user, amountLiq, shares, tokenId } = ev.args;
            staked = {
                user,
                amountLiq: BigNumber.from(amountLiq),
                shares: BigNumber.from(shares),
                tokenId: BigNumber.from(tokenId)
            };
        }

        return {
            txHash: tx.hash,
            receipt,
            staked,
        };
    }



    /**
     * 
     * @param amountWei an amount of liqETH (in WEI) to stake to the Outpost
     * @returns txHash (hash of the transaction), receipt, WithdrawnStake event
     */
    async performWithdrawStake(tokenId: BigNumber, recipient: string): Promise<WithdrawnStakeResult> {
        // Send the payable tx
        const tx = await this.contract.Depositor.withdrawStake(tokenId, recipient);

        // Wait for 1 confirmation 
        const receipt = await tx.wait(1);

        // Parse Staked event if present
        let withdrawnstake: WithdrawnStakeEvent | undefined;
        const ev = receipt.events?.find((e) => e.event === 'WithdrawnStake');

        if (ev && ev.args) {
            const { user, tokenOut, tokenId } = ev.args;
            withdrawnstake = {
                user: user,
                tokenOut: BigNumber.from(tokenOut),
                tokenId: BigNumber.from(tokenId)
            };
        }

        return {
            txHash: tx.hash,
            receipt,
            withdrawnstake,
        };
    }
}