import { bcs } from '@mysten/sui/bcs'
import { SuiClient } from '@mysten/sui/client'
import { Transaction, TransactionArgument, TransactionResult } from '@mysten/sui/transactions'

import { ModuleManager } from '../../module-manager'
import { ObjectOptions } from '../../types'
import { asAddress, asBytes, asBytes32, asObject, asU64, executeSimulate } from '../../utils'

const MODULE_NAME = 'simple_message_lib'

export const SimpleMessageLibErrorCode = {
    // SimpleMessageLib related errors (matching simple_message_lib.move)
    SimpleMessageLib_ENotImplemented: 1,
    SimpleMessageLib_EZROFeeNotEnabled: 2,
    SimpleMessageLib_EInvalidEid: 3,
} as const

export class SimpleMessageLib {
    public packageId: string
    public readonly client: SuiClient
    private readonly objects: ObjectOptions

    constructor(
        packageId: string,
        client: SuiClient,
        objects: ObjectOptions,
        private readonly moduleManager: ModuleManager
    ) {
        this.packageId = packageId
        this.client = client
        this.objects = objects
    }

    // === Set Functions ===

    /**
     * Set messaging fees for simple message library (admin only)
     * @param tx - The transaction to add the move call to
     * @param zroFee - The ZRO token fee amount or transaction argument
     * @param nativeFee - The native token fee amount or transaction argument
     */
    setMessagingFeeMoveCall(
        tx: Transaction,
        zroFee: number | TransactionArgument,
        nativeFee: number | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('set_messaging_fee'),
            arguments: [
                tx.object(this.objects.simpleMessageLib),
                tx.object(this.objects.simpleMessageLibAdminCap),
                asU64(tx, zroFee),
                asU64(tx, nativeFee),
            ],
        })
    }

    /**
     * Set fee recipient address (admin only)
     * @param tx - The transaction to add the move call to
     * @param feeRecipient - The new fee recipient address or transaction argument
     */
    setFeeRecipientMoveCall(tx: Transaction, feeRecipient: string | TransactionArgument): void {
        tx.moveCall({
            target: this.#target('set_fee_recipient'),
            arguments: [
                tx.object(this.objects.simpleMessageLib),
                tx.object(this.objects.endpointAdminCap),
                asAddress(tx, feeRecipient),
            ],
        })
    }

    /**
     * Validate packet for simple message processing
     * @param tx - The transaction to add the move call to
     * @param messagingChannel - The messaging channel object ID or transaction argument
     * @param packetHeader - The packet header as bytes or transaction argument
     * @param payloadHash - The payload hash as bytes or transaction argument
     */
    validatePacketMoveCall(
        tx: Transaction,
        messagingChannel: string | TransactionArgument,
        packetHeader: Uint8Array | TransactionArgument,
        payloadHash: Uint8Array | TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('validate_packet'),
            arguments: [
                tx.object(this.objects.simpleMessageLib),
                tx.object(this.objects.endpointV2),
                tx.object(this.objects.simpleMessageLibAdminCap),
                asObject(tx, messagingChannel),
                asBytes(tx, packetHeader),
                asBytes32(tx, payloadHash, this.moduleManager.getUtils()),
                tx.object.clock(),
            ],
        })
    }

    /**
     * Set configuration for simple message library
     * @param tx - The transaction to add the move call to
     * @param call - The call transaction result containing configuration or transaction argument
     */
    setConfigMoveCall(tx: Transaction, call: TransactionArgument): void {
        tx.moveCall({
            target: this.#target('set_config'),
            arguments: [tx.object(this.objects.simpleMessageLib), call],
        })
    }

    /**
     * Quote messaging fees for simple message library
     * @param tx - The transaction to add the move call to
     * @param call - The call transaction result containing quote parameters or transaction argument
     */
    quoteMoveCall(tx: Transaction, call: TransactionArgument): void {
        tx.moveCall({
            target: this.#target('quote'),
            arguments: [tx.object(this.objects.simpleMessageLib), call],
        })
    }

    /**
     * Send message through simple message library
     * @param tx - The transaction to add the move call to
     * @param messageChannel - The messaging channel object ID or transaction argument
     * @param endpointCall - The endpoint call transaction result or transaction argument
     * @param messageLibCall - The message library call transaction result or transaction argument
     */
    sendMoveCall(
        tx: Transaction,
        messageChannel: string | TransactionArgument,
        endpointCall: TransactionArgument,
        messageLibCall: TransactionArgument
    ): void {
        tx.moveCall({
            target: this.#target('send'),
            arguments: [
                tx.object(this.objects.simpleMessageLib),
                tx.object(this.objects.endpointV2),
                asObject(tx, messageChannel),
                endpointCall,
                messageLibCall,
            ],
        })
    }

    // === View Functions ===

    /**
     * Get native token fee amount
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the native fee
     */
    getNativeFeeMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('get_native_fee'),
            arguments: [tx.object(this.objects.simpleMessageLib)],
        })
    }

    /**
     * Get native token fee amount
     * @returns Promise<string> - The native token fee as string
     */
    async getNativeFee(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.getNativeFeeMoveCall(tx)
            },
            (result) => bcs.U64.parse(result[0].value)
        )
    }

    /**
     * Get ZRO token fee amount
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the ZRO fee
     */
    getZroFeeMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('get_zro_fee'),
            arguments: [tx.object(this.objects.simpleMessageLib)],
        })
    }

    /**
     * Get ZRO token fee amount
     * @returns Promise<string> - The ZRO token fee as string
     */
    async getZroFee(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.getZroFeeMoveCall(tx)
            },
            (result) => bcs.U64.parse(result[0].value)
        )
    }

    /**
     * Get fee recipient address
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the fee recipient address
     */
    getFeeRecipientMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('get_fee_recipient'),
            arguments: [tx.object(this.objects.simpleMessageLib)],
        })
    }

    /**
     * Get fee recipient address
     * @returns Promise<string> - The fee recipient address
     */
    async getFeeRecipient(): Promise<string> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.getFeeRecipientMoveCall(tx)
            },
            (result) => bcs.Address.parse(result[0].value)
        )
    }

    /**
     * Get simple message library version
     * @param tx - The transaction to add the move call to
     * @returns Transaction result containing the version information
     */
    versionMoveCall(tx: Transaction): TransactionResult {
        return tx.moveCall({
            target: this.#target('version'),
            arguments: [],
        })
    }

    /**
     * Get simple message library version
     * @returns Promise<[string, string, string]> - Tuple of [major, minor, endpoint] version strings
     */
    async getVersion(): Promise<[string, string, string]> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.versionMoveCall(tx)
            },
            (result) => [
                bcs.U64.parse(result[0].value).toString(),
                bcs.U8.parse(result[1].value).toString(),
                bcs.U8.parse(result[2].value).toString(),
            ]
        )
    }

    /**
     * Generate the full target path for move calls
     * @param name - The function name to call
     * @param module_name - The module name (defaults to MODULE_NAME)
     * @returns The full module path for the move call
     * @private
     */
    #target(name: string, module_name: string = MODULE_NAME): string {
        return `${this.packageId}::${module_name}::${name}`
    }
}
