
import { ethers } from "ethers";
import { TrancheLadderItem, TrancheSnapshot } from "../../types";
import { ChainID } from "@wireio/core";


const BPS = BigInt(10_000);

export interface CustomContractError {
    name?: string;
    signature?: string;
    args?: any[];
    data?: any;
    method?: string;
    code?: any;
    raw: string;
}



export function formatContractErrors(err: any): CustomContractError {
    // Extract custom error details if present
    if (err.errorName && err.errorArgs) {
        const errorObj: CustomContractError = {
            name: err.errorName,
            signature: err.errorSignature,
            args: err.errorArgs.map((arg: any) =>
                arg && arg._isBigNumber ? ethers.BigNumber.from(arg).toString() : arg
            ),
            data: err.data,
            method: err.method,
            code: err.code,
            raw: err,
        };
        console.error("Custom contract error:", errorObj);
        return errorObj;
    } else {
        console.error("Contract Error:", err);
        return {
            raw: typeof err === 'string' ? err : (err?.message || String(err))
        };
    }
}



/**
 * Finalize an OPP epoch. Optionally accept a gasLimit override (ethers BigNumberish).
 * If gasLimit is not provided, the method will attempt estimateGas.finalizeEpoch()
 * and pad it (1.2x). If estimateGas fails, a conservative fallback is used.
 */
export async function sendOPPFinalize(opp: ethers.Contract, gasLimit?: ethers.BigNumberish) {
    const overrides: any = {};
    try {
        if (gasLimit === undefined) {
            try {
                const estimated = await opp.estimateGas.finalizeEpoch();
                const padded = ethers.BigNumber.from(estimated).mul(12).div(10); // 1.2x
                overrides.gasLimit = padded;
                console.log('sendFinalize: estimated gas', estimated.toString(), 'padded to', overrides.gasLimit.toString());
            } catch (estErr) {
                // estimateGas can throw UNPREDICTABLE_GAS_LIMIT; fall back to a safe default
                console.warn('sendFinalize: estimateGas.finalizeEpoch() failed, falling back to hardcoded gasLimit', estErr);
                overrides.gasLimit = ethers.BigNumber.from(8000000);
            }
        } else {
            overrides.gasLimit = ethers.BigNumber.from(gasLimit);
            console.log('sendFinalize: using provided gasLimit override', overrides.gasLimit.toString());
        }

        const tx = await opp.finalizeEpoch(overrides); // submit tx with overrides
        console.log('sendFinalize tx hash:', tx.hash, 'overrides:', overrides);

        const receipt = await tx.wait(); // wait for mined
        console.log('sendFinalize tx mined, block:', receipt.blockNumber);
        return receipt;
    } catch (err: any) {
        // Verbose error logging to help debugging reverts / provider issues
        console.error('sendFinalize() failed:', err?.message || err);

        // Try to extract common raw payload locations used by ethers errors
        const raw = err?.error?.data || err?.data || err?.body || err?.receipt || err?.transaction || err;
        console.error('sendFinalize raw payload:', raw);

        // Try parsing with contractService if available
        try {
            const parsed = formatContractErrors(raw);
            console.error('sendFinalize parsed error:', parsed.name, parsed.args);
        } catch (parseErr) {
            // Fallback: decode Error(string) ABI encoded revert (0x08c379a0)
            try {
                const hex = (typeof raw === 'string') ? raw : (raw && raw.data) ? raw.data : null;
                if (hex && typeof hex === 'string' && hex.startsWith('0x08c379a0')) {
                    const reason = ethers.utils.defaultAbiCoder.decode(['string'], '0x' + hex.slice(10))[0];
                    console.error('sendFinalize revert reason:', reason);
                } else {
                    console.error('sendFinalize: unable to decode revert payload (not standard Error(string))');
                }
            } catch (fallbackErr) {
                console.error('sendFinalize: fallback decode failed:', fallbackErr);
            }
        }

        // If there is a receipt, print it for extra context
        if (err?.receipt) console.error('sendFinalize receipt:', err.receipt);
        if (err?.transaction) console.error('sendFinalize transaction object:', err.transaction);

        // Re-throw so callers can handle it as well
        throw err;
    }
}



/**
 * Apply one forward growth step: value * (BPS + growthBps) / BPS.
 * Simple integer round-half-up.
 */
function growOnce(value: bigint, growthBps: number): bigint {
    const g = BigInt(growthBps);
    return (value * (BPS + g) + BPS / BigInt(2)) / BPS;
}


/**
 * Apply one backward step: value * BPS / (BPS + growthBps).
 * Also integer round-half-up.
 */
function shrinkOnce(value: bigint, growthBps: number): bigint {
    const g = BigInt(growthBps);
    return (value * BPS + (BPS + g) / BigInt(2)) / (BPS + g);
}


/**
 * Calculate the full supply for a given tranche using BigInt math.
 * trancheNumber is 1-based (tranche 1 = startSupply)
 */
function getTrancheSize(startSupply: bigint, supplyGrowthBps: number, trancheNumber: number): bigint {
    let supply = startSupply;
    for (let i = 0; i < trancheNumber; i++) {
        supply = (supply * (BPS + BigInt(supplyGrowthBps)) + BPS / BigInt(2)) / BPS;
    }
    return supply;
}


/**
 * Build a local tranche ladder around the current tranche
 * using only on-chain config + current state.
 *
 */
export function buildEthereumTrancheLadder(options: {
    currentTranche: number;
    totalTrancheSupply: bigint,
    initialTrancheSupply: bigint;
    currentTrancheSupply: bigint;
    currentPriceUsd: bigint;
    supplyGrowthBps: number;
    priceGrowthBps: number;
    windowBefore?: number;
    windowAfter?: number;
}): TrancheLadderItem[] {
    const {
        currentTranche,
        initialTrancheSupply,
        currentTrancheSupply,
        currentPriceUsd,
        supplyGrowthBps,
        priceGrowthBps,
        windowBefore = 5,
        windowAfter = 5,
    } = options;

    const startId = Math.max(0, currentTranche - windowBefore);
    const endId = currentTranche + windowAfter;
    
    //calculate total tranche size (e.g. 60,600 on tranche 2)
    const currentTrancheSize = getTrancheSize(initialTrancheSupply, supplyGrowthBps, currentTranche);

    const capacity = new Map<number, bigint>();
    const price = new Map<number, bigint>();

    // Seed current
    capacity.set(currentTranche, currentTrancheSize);
    price.set(currentTranche, currentPriceUsd);

    // Forward (future tranches)
    for (let id = currentTranche + 1; id <= endId; id++) {
        const prevCap = capacity.get(id - 1)!;
        const prevPrice = price.get(id - 1)!;
        capacity.set(id, growOnce(prevCap, supplyGrowthBps));
        price.set(id, growOnce(prevPrice, priceGrowthBps));
    }

    // Backward (past tranches)
    for (let id = currentTranche - 1; id >= startId; id--) {
        const nextCap = capacity.get(id + 1)!;
        const nextPrice = price.get(id + 1)!;
        capacity.set(id, shrinkOnce(nextCap, supplyGrowthBps));
        price.set(id, shrinkOnce(nextPrice, priceGrowthBps));
    }

    const ladder: TrancheLadderItem[] = [];
    for (let id = startId; id <= endId; id++) {
        const cap = capacity.get(id)!;
        let sold: bigint;
        if (id < currentTranche) {
            sold = cap;
        } else if (id === currentTranche) {
            sold = cap - currentTrancheSupply;
        } else {
            sold = BigInt(0);
        }

        ladder.push({
            id,
            capacity: cap,
            sold,
            remaining: cap - sold,
            priceUsd: price.get(id)!,
        });
    }

    return ladder;
}


/**
 * Turn raw liqsol_core accounts into a chain-agnostic TrancheSnapshot for SOL.
 * All math stays here; TokenClient just wires accounts + connection.
 */
export async function buildEthereumTrancheSnapshot(options: {
    chainID: ChainID;
    totalSharesBn;
    indexBn;
    trancheNumberBn;
    currentTrancheSupply;
    tranchePriceWadBn;
    totalTrancheSupply;
    initialTrancheSupply;
    supplyGrowthBps;
    priceGrowthBps;
    minPriceUsd;
    maxPriceUsd;

    ethPriceUsd?: bigint;
    nativePriceTimestamp?: number;
    ladderWindowBefore?: number;
    ladderWindowAfter?: number;
}): Promise<TrancheSnapshot> {
    const {
        chainID,
        ethPriceUsd,
        nativePriceTimestamp,
        ladderWindowBefore,
        ladderWindowAfter,

        totalSharesBn, 
        indexBn, 
        trancheNumberBn, 
        currentTrancheSupply, 
        tranchePriceWadBn, 
        totalTrancheSupply,
        initialTrancheSupply,
        supplyGrowthBps,
        priceGrowthBps,
        minPriceUsd, 
        maxPriceUsd,
    } = options;


    // convert default BigNumber to bigint for hub to handle, and partially convert from 1e18 to 1e8 for the hub
    const totalShares = BigInt(totalSharesBn.toString()) / BigInt(1e10);
    const currentIndex = BigInt(indexBn.toString()); // RAY (1e27)
    const currentTranche = Number(trancheNumberBn.toString());
    const currentPriceUsd = BigInt(tranchePriceWadBn.toString()) / BigInt(1e10); // 1e18 WAD


    const ladder = buildEthereumTrancheLadder({
        currentTranche,
        totalTrancheSupply,
        initialTrancheSupply,
        currentTrancheSupply,
        currentPriceUsd,
        supplyGrowthBps,
        priceGrowthBps,
        windowBefore: ladderWindowBefore,
        windowAfter: ladderWindowAfter,
    });


    return {
        chainID,
        currentIndex,
        totalShares,
        currentTranche,
        currentPriceUsd,
        supplyGrowthBps,
        priceGrowthBps,
        currentTrancheSupply,
        initialTrancheSupply,
        totalPretokensSold: totalTrancheSupply,
        nativePriceUsd: ethPriceUsd,
        nativePriceTimestamp,
        ladder,
    };
}
