import { BigNumber, ethers } from "ethers";
import { DepositEvent, DepositResult, SharesBurnedEvent } from "../types";
import { EthereumContractService } from "../contract";
import { formatContractErrors } from "../utils";

export class ConvertClient {

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

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

    /**
     * Simulate a deposit via callStatic.
     *
     * Useful for pre-flight checks; will throw with the same revert
     * reason as a real tx if it would fail.
     */
    async simulateDeposit(amount: number | string | bigint | BigNumber): Promise<void> {
        const amountWei = BigNumber.isBigNumber(amount)
            ? amount
            : BigNumber.from(amount);

        // callStatic executes the function locally without sending a tx.
        // deposit() doesn't return anything, so we only care if it reverts.
        await this.contract.DepositManager.callStatic.deposit({
            value: amountWei,
        });
    }



    /**
     * 
     * @param amountWei an amount of WEI to deposit in exchange for liqETH
     * @returns the hash of the transaction
     */
    async performDeposit(amountWei: BigNumber): Promise<DepositResult> {
        // Pre-check minDeposit
        const minDeposit: BigNumber = await this.contract.DepositManager.minDeposit();
        if (amountWei.lt(minDeposit)) {
            console.warn('less than min deposit');
            throw new Error(
                `Deposit amount below minDeposit: ` +
                `amount=${ethers.utils.formatEther(amountWei)} ETH, ` +
                `min=${ethers.utils.formatEther(minDeposit)} ETH`
            );
        }

        let tx, receipt;
        try {
            tx = await this.contract.DepositManager.deposit({ value: amountWei });
            receipt = await tx.wait(1);
        } catch (err: any) {
            let errorObj = formatContractErrors(err);
            throw new Error(errorObj.name ?? errorObj.raw)
        }


        // Parse Deposited event if present
        let deposited: DepositEvent | undefined;
        const ev = receipt.events?.find((e) => e.event === 'Deposited');

        if (ev && ev.args) {
            const { user, netEth, fee, shares } = ev.args;
            deposited = {
                user,
                netEth: BigNumber.from(netEth),
                fee: BigNumber.from(fee),
                shares: BigNumber.from(shares),
            };
        }

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


    public async performWithdraw(signerAddress: string, amountWei: BigNumber): Promise<any> {
        let tx, receipt;
        try {
            tx = await this.contract.LiqEthToken.safeBurn(signerAddress, amountWei);
            receipt = await tx.wait(1);
        } catch (err: any) {
            let errorObj = formatContractErrors(err);
            throw new Error(errorObj.name ?? errorObj.raw)
        }

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

        if (ev && ev.args) {
            const { from, shares, tokenValue } = ev.args;
            event = {
                from,
                shares: BigNumber.from(shares),
                tokenValue: BigNumber.from(tokenValue),
            };
        }

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


    
    /*
    // OLD - this was replaced with LiqEth.safeBurn() on 1/13/26
    async requestWithdraw(amountWei: BigNumber, signer: Signer, chainId: ChainID): Promise<WithdrawResult> {
        // deadline is a period of time in the future that the signature is valid for
        const deadline = Math.floor(Date.now() / 1000) + 3600;

        const liqEth = this.contract.LiqEth;
        const owner = await signer.getAddress();
        
        const nonce: BigNumber = await liqEth.nonces(owner);
        const domain = {
            name: await liqEth.name(),
            version: '1',
            chainId,
            verifyingContract: this.contract.LiqEthToken.address,
        } as any;

        const types = {
            Permit: [
                { name: 'owner', type: 'address' },
                { name: 'spender', type: 'address' },
                { name: 'value', type: 'uint256' },
                { name: 'nonce', type: 'uint256' },
                { name: 'deadline', type: 'uint256' },
            ],
        } as any;

        const values = {
            owner,
            spender: this.contractService.getAddress('DepositManager'),
            value: amountWei,
            nonce: nonce,
            deadline,
        } as any;

        const signature = await (signer as any)._signTypedData(domain, types, values);
        const split = ethers.utils.splitSignature(signature);

        const tx = await this.contract.DepositManager.requestWithdrawal(
            amountWei,
            deadline,
            split.v,
            split.r,
            split.s
        );

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

        // if WithdrawRequested event exists, parse it and get arguments
        let withdrawRequested: WithdrawRequestedEvent | undefined;
        const ev = receipt.events?.find((e) => e.event === 'WithdrawRequested');

        if (ev && ev.args) {
            const { user, ethAmount, nftId, readyAt } = ev.args;
            withdrawRequested = {
                user,
                ethAmount: BigNumber.from(ethAmount),
                nftId: BigNumber.from(nftId),
                readyAt: readyAt,
            };
        }

        return {
            txHash: tx.hash,
            receipt,
            withdrawRequested,
        } as WithdrawResult;
    }
    */




}