import {PrivateKey, TransactionBuilder} from "libnexa-ts";
import {rostrumProvider} from "../../network/RostrumProvider";
import {
    buildCreateGroupTransaction,
    populateAndDuplicateTokenAuths,
    populateNexaInputsAndChange,
    populateTokenAuth,
    populateTokenInputsAndChange,
    prepareDeleteTransaction
} from "../../utils/TXUtils";
import {BaseAccount} from "../accounts/interfaces/BaseAccountInterface";
import {AddressKey} from "../../models/wallet.entities";
import {TransactionCreator} from "./interfaces/TransactionCreator";
import {keys} from "lodash-es";
import {PermissionLabel} from "../../models/transaction.entities";

export default class WalletTransactionCreator extends TransactionCreator {

    private  _keysToSign: PrivateKey[] = [];
    private _account!: BaseAccount;

    constructor(fromAccount: BaseAccount, tx?: TransactionBuilder | string | Buffer) {
        super(tx)
        this._account = fromAccount
        this.validateAccount()
    }

    public fromAccount(fromAccount: BaseAccount): this {
        this._account = fromAccount
        return this
    }

    public parseTxHex(tx: string) {
        this.builder.push(async () => {
            const txBuilder = new TransactionBuilder(tx)
            const newTxBuilder = new TransactionBuilder()
            const oldInputs = txBuilder.transaction.inputs
            for (const input of oldInputs) {
                const utxo = await rostrumProvider.getUtxo(input.outpoint.toString('hex'))
                newTxBuilder.from({
                    outpoint: utxo.tx_hash,
                    amount: utxo.amount,
                    scriptPubKey: utxo.scriptpubkey
                })
                const foundKey = this.findPrivateKeyFromAddress(utxo.addresses[0])
                if(foundKey) {
                    this._keysToSign.push(foundKey.key.privateKey)
                }

            }
            newTxBuilder.transaction.outputs = txBuilder.transaction.outputs

        })

        return this
    }

    public parseTxBuffer(tx: Buffer) {
        this.builder.push(async () => {
            this.transactionBuilder = new TransactionBuilder(tx);
        })

        return this
    }

    public mint(token: string, amount: string): this {
        this.builder.push(async() => {
            let toAddr = this._account.accountKeys.receiveKeys.at(-1)!.address;
            this.tokenAction(toAddr, amount, token, 'mint')
        })

        return this
    }

    public melt(token: string, amount: string): this {
        this.builder.push(async() => {
            let toAddr = this._account.accountKeys.receiveKeys.at(-1)!.address;
            this.tokenAction(toAddr, amount, token, 'melt')
        })

        return this
    }

    public populate(): this {
        this.validateAccount();
        this.builder.push(async () => {
            let tK: PrivateKey[] = []
            let nK: PrivateKey[] = []
            if(this.tokens.size > 0) {
                for (const tokenAction of this.tokens) {
                    if(tokenAction.action == 'mint' || tokenAction.action == 'melt') {
                        tK = tK.concat(await populateTokenAuth(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.action))
                    } else if (tokenAction.action == 'group') {
                        tK = tK.concat(await buildCreateGroupTransaction(this.transactionBuilder, this._account.accountKeys, tokenAction.extraData?.opReturnData!, this.network))
                    } else if (tokenAction.action == 'subgroup') {
                       tK = tK.concat(await populateTokenAuth(this.transactionBuilder, this._account.accountKeys, tokenAction.parentToken!, 'subgroup', tokenAction.token, this._account.accountKeys.receiveKeys.at(-1)!.address));
                    } else if(tokenAction.action == 'renew') {
                        tK = tK.concat(await populateAndDuplicateTokenAuths(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.extraData!.perms!, tokenAction.extraData!.address!))
                    } else if(tokenAction.action == 'delete') {
                        tK = tK.concat(await prepareDeleteTransaction(this.transactionBuilder, this._account.accountKeys, tokenAction.extraData!.outpoint!))
                    } else {
                        tK = tK.concat(await populateTokenInputsAndChange(this.transactionBuilder, this._account.accountKeys, tokenAction.token!, tokenAction.amount))
                    }

                    this._keysToSign!.concat(tK)
                }
            }

            nK = nK.concat(await populateNexaInputsAndChange(this.transactionBuilder, this._account.accountKeys, this.totalValue, this.txOptions))
            this._keysToSign! = tK.concat(nK)
        })

        return this
    }

    public sign(): this {
        this.builder.push(async () => {
            const blockHeight = await rostrumProvider.getBlockTip()
            this.transactionBuilder.lockUntilBlockHeight(blockHeight.height).sign(this._keysToSign!)
        })

        return this
    }

    /**
     * Validates that the account has the necessary keys before performing operations
     * @throws Error if account or keys are not properly initialized
     */
    private validateAccount(): void {
        if (!this._account) {
            throw new Error('Account must be set before performing transactions');
        }
        if (!this._account.accountKeys) {
            throw new Error('Account keys are not initialized');
        }
        if (!this._account.accountKeys.receiveKeys || this._account.accountKeys.receiveKeys.length === 0) {
            throw new Error('No receive keys available in account');
        }
    }

    private findPrivateKeyFromAddress(addr: string): AddressKey | undefined {
        const keys: AddressKey[] = this._account.accountKeys.receiveKeys.concat(this._account.accountKeys.changeKeys)
        return keys.find(key => key.address === addr)
    }
}
