
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;
}

const DEFAULT_WRITE_GAS_PADDING_BPS = 2_000;
const DEFAULT_WRITE_GAS_FALLBACK_LIMIT = ethers.BigNumber.from(8_000_000);



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;
    }
}

export async function resolveContractWriteOverrides(
    contract: ethers.Contract,
    method: string,
    args: any[] = [],
    overrides: Record<string, unknown> = {},
    options?: {
        fallbackGasLimit?: ethers.BigNumberish;
        paddingBps?: number;
    },
): Promise<Record<string, unknown>> {
    if (overrides['gasLimit'] != null) {
        return overrides;
    }

    const paddingBps = options?.paddingBps ?? DEFAULT_WRITE_GAS_PADDING_BPS;
    const fallbackGasLimit = ethers.BigNumber.from(
        options?.fallbackGasLimit ?? DEFAULT_WRITE_GAS_FALLBACK_LIMIT,
    );

    try {
        const estimateGas = contract.estimateGas?.[method];
        if (typeof estimateGas !== 'function') {
            return {
                ...overrides,
                gasLimit: fallbackGasLimit,
            };
        }

        const estimated = await estimateGas(...args, overrides);
        const gasLimit = ethers.BigNumber.from(estimated)
            .mul(10_000 + paddingBps)
            .div(10_000);

        return {
            ...overrides,
            gasLimit,
        };
    } catch (error) {
        console.warn(
            `[EthereumGas] estimateGas.${method} failed, using fallback gasLimit ${fallbackGasLimit.toString()}`,
            error,
        );
        return {
            ...overrides,
            gasLimit: fallbackGasLimit,
        };
    }
}


const BPS_DENOM = BigInt(10_000);

// On-chain USD: 1e18 (wei-style)
const USD_ONCHAIN_SCALE = BigInt(1_000_000_000_000_000_000); // 1e18
// Client snapshot USD: 1e8 (to match fromScale8)
const USD_CLIENT_SCALE = BigInt(100_000_000);                // 1e8
// Factor to go from 1e18 → 1e8
const USD_SCALE_DOWN = USD_ONCHAIN_SCALE / USD_CLIENT_SCALE; // 1e10

/** 2.5% growth in BPS for supply side */
function growSupplyOnce(value: bigint, growthBps: number): bigint {
    const g = BigInt(growthBps);
    return (value * (BPS_DENOM + g)) / BPS_DENOM;
}

function shrinkSupplyOnce(value: bigint, growthBps: number): bigint {
    const g = BigInt(growthBps);
    return (value * BPS_DENOM) / (BPS_DENOM + g);
}

/** Linear USD price step in on-chain 1e18 scale */
function growPriceOnceUsd1e18(value: bigint, stepUsd1e18: bigint): bigint {
    return value + stepUsd1e18;
}

function shrinkPriceOnceUsd1e18(value: bigint, stepUsd1e18: bigint): bigint {
    if (value <= stepUsd1e18) return BigInt(0);
    return value - stepUsd1e18;
}

/** Convert on-chain 1e18 USD to client 1e8 USD */
function usd1e18To1e8(raw: bigint): bigint {
    // 1e18 / 1e8 = 1e10 factor difference
    return raw / USD_SCALE_DOWN;
}

/**
 * Build a local tranche ladder around the current tranche.
 *
 * Inside this function:
 *  - `currentPriceUsd` and `priceGrowthCents` are treated as 1e18-scaled USD.
 *  - Output `priceUsd` is 1e8-scaled USD (for fromScale8()).
 */
export function buildEthereumTrancheLadder(options: {
    currentTranche: number;
    totalTrancheSupply: bigint;   // not used in local window, but kept for API parity
    initialTrancheSupply: bigint;
    currentTrancheSupply: bigint; // remaining in current tranche (1e8 scale)
    currentPriceUsd: bigint;      // 1e18 scale
    priceGrowthCents: bigint;     // 1e18 step, e.g. $0.02 → 2e16
    supplyGrowthBps: number;      // 250 = 2.5%
    windowBefore?: number;
    windowAfter?: number;
}): TrancheLadderItem[] {
    const {
        currentTranche,
        initialTrancheSupply,
        currentTrancheSupply,
        currentPriceUsd,
        priceGrowthCents,
        supplyGrowthBps,
        windowBefore = 5,
        windowAfter = 5,
    } = options;

    const startId = Math.max(0, currentTranche - windowBefore);
    const endId = currentTranche + windowAfter;

    const capacity = new Map<number, bigint>(); // 1e8 pre-token units
    const priceUsd = new Map<number, bigint>(); // 1e18 USD

    // Capacity at the current tranche derived from initial supply & BPS growth
    let currentCap = initialTrancheSupply;
    for (let i = 0; i < currentTranche; i++) {
        currentCap = growSupplyOnce(currentCap, supplyGrowthBps);
    }

    capacity.set(currentTranche, currentCap);
    priceUsd.set(currentTranche, currentPriceUsd);

    // Forward (future tranches)
    for (let id = currentTranche + 1; id <= endId; id++) {
        const prevCap = capacity.get(id - 1)!;
        const prevPrice = priceUsd.get(id - 1)!;

        capacity.set(id, growSupplyOnce(prevCap, supplyGrowthBps));
        priceUsd.set(id, growPriceOnceUsd1e18(prevPrice, priceGrowthCents));
    }

    // Backward (past tranches)
    for (let id = currentTranche - 1; id >= startId; id--) {
        const nextCap = capacity.get(id + 1)!;
        const nextPrice = priceUsd.get(id + 1)!;

        capacity.set(id, shrinkSupplyOnce(nextCap, supplyGrowthBps));
        priceUsd.set(id, shrinkPriceOnceUsd1e18(nextPrice, priceGrowthCents));
    }

    // Build ladder view
    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);
        }

        const remaining = cap - sold;
        const priceClientScale = usd1e18To1e8(priceUsd.get(id)!); // 1e8

        ladder.push({
            id,
            capacity: cap,
            sold,
            remaining,
            priceUsd: priceClientScale,
        });
    }

    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;
    tranchePriceUsdBn;
    totalTrancheSupply;
    initialTrancheSupply;
    supplyGrowthBps;
    priceGrowthCents;  // BigNumber from contract (1e18 for $0.02)
    minPriceUsd;
    maxPriceUsd;

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

        totalSharesBn,
        indexBn,
        trancheNumberBn,
        currentTrancheSupply,
        tranchePriceUsdBn,
        totalTrancheSupply,
        initialTrancheSupply,
        supplyGrowthBps,
        priceGrowthCents,
        minPriceUsd,
        maxPriceUsd,
    } = options;

    // ---- BigNumber -> bigint conversions ----

    // Shares: keep your prior behaviour (1e8 scale) via /1e10
    const totalShares = BigInt(totalSharesBn.toString()) / BigInt(10_000_000_000); // 1e10

    const currentIndex = BigInt(indexBn.toString()); // RAY (1e27)

    const currentTranche = Number(trancheNumberBn.toString());

    // Prices & step in 1e18 scale from contract
    const currentPriceUsd1e18 = BigInt(tranchePriceUsdBn.toString());
    const priceGrowthStepUsd1e18 = BigInt(priceGrowthCents.toString());

    // Convert price step to “cents” number for snapshot:
    // 1 USD = 1e18 → 1 cent = 1e16.
    const priceGrowthCentsNumber = Number(
        priceGrowthStepUsd1e18 / BigInt(10_000_000_000_000_000) // 1e16
    );

    // Pre-token supplies (already 1e8-ish scale on-chain)
    const currentTrancheSupplyBig = BigInt(currentTrancheSupply.toString());
    const totalTrancheSupplyBig = BigInt(totalTrancheSupply.toString());
    const initialTrancheSupplyBig = BigInt(initialTrancheSupply.toString());

    // UI-current price (1e8 scale)
    const currentPriceUsd = currentPriceUsd1e18 / BigInt(10_000_000_000); // 1e10

    // ---- Build ladder ----

    const ladder = buildEthereumTrancheLadder({
        currentTranche,
        totalTrancheSupply: totalTrancheSupplyBig,
        initialTrancheSupply: initialTrancheSupplyBig,
        currentTrancheSupply: currentTrancheSupplyBig,
        currentPriceUsd: currentPriceUsd1e18,      // 1e18
        priceGrowthCents: priceGrowthStepUsd1e18,  // 1e18 step
        supplyGrowthBps,
        windowBefore: ladderWindowBefore,
        windowAfter: ladderWindowAfter,
    });

    return {
        chainID,
        currentIndex,
        totalShares,
        currentTranche,
        currentPriceUsd,                // 1e8
        supplyGrowthBps,
        priceGrowthCents: priceGrowthCentsNumber, // <-- number as required
        totalPretokensSold: totalTrancheSupplyBig,
        currentTrancheSupply: currentTrancheSupplyBig,
        initialTrancheSupply: initialTrancheSupplyBig,
        nativePriceUsd: ethPriceUsd,
        nativePriceTimestamp,
        ladder,
    };
}
