import { BigNumber, ethers } from "ethers";
import { EthereumContractService } from "../contract";
import { formatContractErrors, sendOPPFinalize } from "../utils";

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

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


    /**
     * Purchase pretokens by transferring liqETH into the pool (ERC-20) and calling Depositor.purchasePretokensWithLiqETH(amountLiq, buyer).
     * Returns txHash, receipt and parsed PurchasedWithLiqETH event (if present).
     */
    async purchasePretokensWithLiqETH(amountLiq: bigint, buyer: string): Promise<any> {

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

        // if current liq balance is less than the requested buy amount, throw an error
        if (bal.lt(amountLiq)) {
            throw new Error(`Balance insufficient for pre-token purchase`);
        }

        //check that the contract has allowance for the token
        const depositorAddr = this.contract.Depositor.address;
        const allowance = await this.contract.LiqEthToken.allowance(buyer, depositorAddr);

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

            console.warn(`allowance insufficient (${allowance.toString()} < ${amountLiq.toString()}); sending approve(${depositorAddr}, ${amountLiq.toString()})`);

            const approveTx = await liqWrite.approve(depositorAddr, amountLiq);
            await approveTx.wait(1);

            // re-read allowance to ensure approval succeeded
            const newAllowance = await this.contract.LiqEthToken.allowance(buyer, depositorAddr);
            if (newAllowance.lt(amountLiq)) {
                throw new Error('Approval failed or allowance still insufficient after approve');
            }
        }
        
        // attempt a simulation of the purchase call
        try {
            const amountLiqBN = BigNumber.from(amountLiq)
            await this.contract.Depositor.callStatic.purchasePretokensWithLiqETH(amountLiqBN, buyer);
        } catch (err: any) {
            console.log('err', err);
            
            let errorObj = formatContractErrors(err);

            // ! TEMP for testing
            if(errorObj.name == "OPP_PreviousEpochUnsent") {
                console.error("OPP Previous Epoch is unsent - trigger OPP finalizeEpoch()")
                await sendOPPFinalize(this.contract.OPP);
                throw new Error(errorObj.name+` - triggering OPP finalizeEpoch() please try again`);
            }

            throw new Error(errorObj.name ?? errorObj.raw)
        }

        // attempt the real purchase call
        let tx, receipt;
        try {
            tx = await this.contract.Depositor.purchasePretokensWithLiqETH(amountLiq, buyer);
            receipt = await tx.wait(1);
        } catch (err: any) {
            let errorObj = formatContractErrors(err);
            throw new Error(errorObj.name ?? errorObj.raw)
        }


        // Attempt to parse PurchasedWithLiqETH event
        let purchased: any | undefined;
        const ev = receipt.events?.find((e) => e.event === 'PurchasedWithLiqETH' || (e.event && e.event.toString().startsWith('PurchasedWithLiqETH')));

        if (ev && ev.args) {
            // Event shape: PurchasedWithLiqETH(buyer, amountLiq, ethEqWei, shares, tokenId)
            const args: any = ev.args;
            // try named fields first, fallback to positional
            const evtBuyer = args.buyer ?? args[0];
            const evtAmountLiq = (args.amountLiq ?? args[1]) ? BigNumber.from(args.amountLiq ?? args[1]) : undefined;
            const evtEthEqWei = (args.ethEqWei ?? args[2]) ? BigNumber.from(args.ethEqWei ?? args[2]) : undefined;
            const evtShares = (args.shares ?? args[3]) ? BigNumber.from(args.shares ?? args[3]) : undefined;
            const evtTokenId = (args.tokenId ?? args[4]) ? BigNumber.from(args.tokenId ?? args[4]) : undefined;

            purchased = {
                buyer: evtBuyer,
                amountLiq: evtAmountLiq,
                ethEqWei: evtEthEqWei,
                shares: evtShares,
                tokenId: evtTokenId,
            };
        }

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




    /**
     * Purchase pretokens by sending ETH to the Depositor.purchasePretokensWithETH(buyer) payable function.
     * Returns txHash, receipt and parsed PurchasedWithETH event (if present).
    async purchasePretokensWithETH(amountWei: BigNumber, buyer: string): Promise<any> {
        // attempt a simulation of the purchase call
        try {
            await this.contract.Depositor.callStatic.purchasePretokensWithETH(buyer, { value: amountWei });
        } catch (err: any) {
            let errorObj = formatContractErrors(err);
            throw new Error(errorObj.name ?? errorObj.raw)
        }

        // attempt the real purchase call
        let tx, receipt;
        try {
            tx = await this.contract.Depositor.purchasePretokensWithETH(buyer, { value: amountWei });
            receipt = await tx.wait(1);
        } catch (err: any) {
            let errorObj = formatContractErrors(err);
            throw new Error(errorObj.name ?? errorObj.raw)
        }


        // Attempt to parse PurchasedWithETH event
        let purchased: any | undefined;
        const ev = receipt.events?.find((e) => e.event === 'PurchasedWithETH' || e.event === 'PurchasedWithETH(address,uint256,uint256,uint256)');

        if (ev && ev.args) {
            // event signature: PurchasedWithETH(address indexed user, uint256 ethIn, uint256 shares, uint256 tokenId)
            const { user, ethIn, shares, tokenId } = ev.args as any;

            purchased = {
                buyer: user,
                amount: BigNumber.from(ethIn),
                shares: BigNumber.from(shares),
                tokenId: BigNumber.from(tokenId),
            };
        }

        return {
            txHash: tx.hash,
            receipt,
            purchased,
        };
    }
     */

    

}