import { BigNumber } from "ethers";
import { preLaunchReceipt } from "../types";
import { EthereumContractService } from "../contract";
import { ReceiptNFTKind } from "../../../types";

export class ReceiptClient {

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

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



    async allReceipts(address: string): Promise<preLaunchReceipt[]> {
        return this.fetchPreLaunchReceipts(address);
    }

    async stakeReceipts(address: string): Promise<preLaunchReceipt[]> {
        return this.fetchPreLaunchReceipts(address, ReceiptNFTKind.STAKE);
    }
    
    async pretokenReceipts(address: string): Promise<preLaunchReceipt[]> {
        return this.fetchPreLaunchReceipts(address, ReceiptNFTKind.PRETOKEN_PURCHASE);
    }



    /**
     * 
     * @param address (string) to fetch receipts for
     * @returns preLaunchReceipt[]
     */
    async fetchPreLaunchReceipts(address: string, type?: ReceiptNFTKind): Promise<preLaunchReceipt[]> {
        const receiptContract = this.contract.ReceiptNFT;

        // first figure out which tokenIds this address owns, from events
        const tokenIds = await this.getOwnedTokenIdsFor(address);

        const results: preLaunchReceipt[] = [];

        // next fetch on-chain receipt data just for those ids
        for (const idBN of tokenIds) {
            try {
                const receiptData = await receiptContract.getReceipt(idBN);

                //skip any receipt not of the requested type
                if(type !== undefined && receiptData.kind !== type) continue;

                results.push({ 
                    tokenId: idBN.toBigInt(), 
                    receipt: {
                        account: receiptData.account,
                        currency: receiptData.currency,
                        kind: receiptData.kind,
                        indexAtMint: receiptData.indexAtMint.toBigInt(),
                        principal: {
                            amount: receiptData.principal.toBigInt(),
                            decimals: 18,
                            symbol: "LiqETH"
                        },
                        shares: {
                            amount: receiptData.shares.toBigInt(),
                            decimals: 18,
                            symbol: "LiqETH"
                        },
                        timestamp: new Date(Number(receiptData.timestamp.toString()) * 1000).toLocaleString(),
                    }
                });
            } catch (err) {
                // in case of any mismatch or race, just skip this id
                console.warn(`Failed to load receipt for tokenId=${idBN.toString()}`, err);
                continue;
            }
        }

        return results;
    }




    private async getOwnedTokenIdsFor(
        owner: string,
        fromBlock = 0,
        toBlock: number | string = "latest"
    ): Promise<BigNumber[]> {
        const receiptContract = this.contract.ReceiptNFT;

        // Logs where address received tokens
        const toLogs = await receiptContract.queryFilter(
            receiptContract.filters.Transfer(null, owner),
            fromBlock,
            toBlock
        );

        // Logs where address sent tokens (including burns from owner → 0)
        const fromLogs = await receiptContract.queryFilter(
            receiptContract.filters.Transfer(owner, null),
            fromBlock,
            toBlock
        );

        const owned = new Set<string>();

        // Add all received tokenIds
        for (const e of toLogs) {
            const tokenId = e.args?.tokenId;
            if (!tokenId) continue;
            owned.add(tokenId.toString());
        }

        // Remove all sent tokenIds
        for (const e of fromLogs) {
            const tokenId = e.args?.tokenId;
            if (!tokenId) continue;
            owned.delete(tokenId.toString());
        }

        // Convert to BigNumbers
        return Array.from(owned).map((id) => BigNumber.from(id));
    }



}