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, executeSimulate } from '../../utils'

const MODULE_NAME = 'package_whitelist_validator'

export const ValidatorErrorCode = {
    // PTB Whitelist related errors
    EInvalidWitness: 1,
} as const

export class PackageWhitelistValidator {
    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
    }

    // === Whitelist Management Functions ===

    /**
     * Add a package to the whitelist using a LayerZero witness
     * This function is permissionless and allows packages to self-register
     * by providing a valid LayerZeroWitness type
     * @param tx - The transaction to add the move call to
     * @param witness - The LayerZero witness from the package
     * @template T - The witness type that must be named "_witness::LayerZeroWitness"
     */
    addWhitelistMoveCall(tx: Transaction, witness: TransactionArgument, witnessType: string): void {
        tx.moveCall({
            target: this.#target('add_whitelist'),
            typeArguments: [witnessType],
            arguments: [tx.object(this.objects.packageWhitelistValidator), witness],
        })
    }

    // === View Functions ===

    /**
     * Check if a package is whitelisted
     * @param tx - The transaction to add the move call to
     * @param packageAddress - The package address to check
     * @returns Transaction result containing the whitelist status
     */
    isWhitelistedMoveCall(tx: Transaction, packageAddress: string | TransactionArgument): TransactionResult {
        return tx.moveCall({
            target: this.#target('is_whitelisted'),
            arguments: [tx.object(this.objects.packageWhitelistValidator), asAddress(tx, packageAddress)],
        })
    }

    /**
     * Validate multiple packages at once
     * @param tx - The transaction to add the move call to
     * @param packageAddresses - Array of package addresses to validate
     * @returns Transaction result containing the validation status
     */
    validateMoveCall(tx: Transaction, packageAddresses: string[]): TransactionResult {
        return tx.moveCall({
            target: this.#target('validate'),
            arguments: [
                tx.object(this.objects.packageWhitelistValidator),
                tx.pure(bcs.vector(bcs.Address).serialize(packageAddresses)),
            ],
        })
    }

    /**
     * Check if a package is whitelisted
     * @param packageAddress - The package address to check
     * @returns Promise<boolean> - True if the package is whitelisted
     */
    async isWhitelisted(packageAddress: string): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.isWhitelistedMoveCall(tx, packageAddress)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    // === Utility Functions ===

    /**
     * Extract package ID from a witness type name
     * @param witnessTypeName - The full type name of the witness
     * @returns string | null - The package ID if extraction is successful
     */
    extractPackageFromWitness(witnessTypeName: string): string | null {
        // Pattern: "0x123abc::my_package_witness::LayerZeroWitness"
        const parts = witnessTypeName.split('::')
        if (parts.length >= 3) {
            const packageId = parts[0]
            if (packageId.startsWith('0x') && packageId.length > 2) {
                return packageId
            }
        }
        return null
    }

    // === Batch Operations ===

    /**
     * Validate multiple packages at once using the contract's validate function
     * @param packageAddresses - Array of package addresses to validate
     * @returns Promise<boolean> - True if all packages are whitelisted
     */
    async validate(packageAddresses: string[]): Promise<boolean> {
        return executeSimulate(
            this.client,
            (tx) => {
                this.validateMoveCall(tx, packageAddresses)
            },
            (result) => bcs.Bool.parse(result[0].value)
        )
    }

    /**
     * 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 = MODULE_NAME): string {
        return `${this.packageId}::${module_name}::${name}`
    }
}
