import fs from 'fs'
import path from 'path'

import { importDefault, Logger } from '@layerzerolabs/io-devtools'
import { EndpointId } from '@layerzerolabs/lz-definitions'
import type { OAppOmniGraphHardhat } from '@layerzerolabs/toolbox-hardhat'

import type { HardhatUserConfig, NetworkUserConfig } from 'hardhat/types'
import type { CoreSpotDeployment, TxData } from '@/types'

function getFullPath(index: string, isTestnet: boolean): string {
    return path.join(process.cwd(), 'deployments', `hypercore-${isTestnet ? 'testnet' : 'mainnet'}`, `${index}.json`)
}

export function getCoreSpotDeployment(index: string | number, isTestnet: boolean, logger?: Logger): CoreSpotDeployment {
    const fullPath = getFullPath(index.toString(), isTestnet)

    // Check if file exists, if not provide helpful error message
    if (!fs.existsSync(fullPath)) {
        const deploymentDir = path.join(process.cwd(), 'deployments', `hypercore-${isTestnet ? 'testnet' : 'mainnet'}`)

        // List available deployment files
        let availableTokens: string[] = []
        try {
            if (fs.existsSync(deploymentDir)) {
                const files = fs.readdirSync(deploymentDir)
                availableTokens = files.filter((f) => f.endsWith('.json')).map((f) => f.replace('.json', ''))
            }
        } catch (e) {
            // Ignore error when listing files
        }

        const network = isTestnet ? 'testnet' : 'mainnet'
        let errMsg = `Core spot deployment file not found for token index ${index} on ${network}.\n`
        errMsg += `Expected location: ${fullPath}\n`

        if (availableTokens.length > 0) {
            errMsg += `\nAvailable token indices on ${network}:\n`
            errMsg += availableTokens.map((t) => `  - ${t}`).join('\n')
        } else {
            errMsg += `\nNo deployment files found in ${deploymentDir}`
            errMsg += `\nYou need to run the core-spot deployment commands first.`
        }

        logger?.error(errMsg)
        throw new Error(errMsg)
    }

    const nativeSpot = fs.readFileSync(fullPath, 'utf8')
    return JSON.parse(nativeSpot) as CoreSpotDeployment
}

export function writeCoreSpotDeployment(
    index: string | number,
    isTestnet: boolean,
    coreSpotDeployment: CoreSpotDeployment,
    logger?: Logger
) {
    const fullPath = getFullPath(index.toString(), isTestnet)

    const dir = path.dirname(fullPath)
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true })
    }

    fs.writeFileSync(fullPath, JSON.stringify(coreSpotDeployment, null, 2))
    logger?.info(`Wrote deployment ${fullPath}`)
}

export function writeUpdatedCoreSpotDeployment(
    index: string | number,
    isTestnet: boolean,
    tokenFullName: string,
    tokenAddress: string,
    txData: TxData,
    logger?: Logger
) {
    const fullPath = getFullPath(index.toString(), isTestnet)

    const spot = getCoreSpotDeployment(index, isTestnet, logger)
    spot.coreSpot.evmContract = {
        address: tokenAddress,
        evm_extra_wei_decimals: txData.weiDiff ?? 0,
    }
    spot.coreSpot.fullName = tokenFullName
    spot.txData.txHash = txData.txHash
    spot.txData.nonce = txData.nonce
    spot.txData.from = txData.from
    spot.txData.connected = txData.connected
    spot.txData.weiDiff = txData.weiDiff

    fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
    logger?.info(`Updated core spot ${index}`)
}

export function writeNativeSpotConnected(
    index: string | number,
    isTestnet: boolean,
    connected: boolean,
    weiDiff: number,
    logger?: Logger
) {
    const fullPath = getFullPath(index.toString(), isTestnet)

    const spot = getCoreSpotDeployment(index, isTestnet, logger)
    spot.txData.connected = connected
    spot.txData.weiDiff = weiDiff

    fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
    logger?.info(`Updated core spot ${index}`)
}

export function updateFreezePrivilegeStatus(
    index: string | number,
    isTestnet: boolean,
    enabled: boolean,
    logger?: Logger
) {
    const fullPath = getFullPath(index.toString(), isTestnet)

    const spot = getCoreSpotDeployment(index, isTestnet, logger)
    spot.coreSpot.freezePrivilegeEnabled = enabled

    fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
    logger?.info(`Updated freeze privilege status for core spot ${index}: ${enabled ? 'enabled' : 'revoked'}`)
}

export function updateQuoteTokenStatus(index: string | number, isTestnet: boolean, enabled: boolean, logger?: Logger) {
    const fullPath = getFullPath(index.toString(), isTestnet)

    const spot = getCoreSpotDeployment(index, isTestnet, logger)
    spot.coreSpot.quoteAssetEnabled = enabled

    fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
    logger?.info(`Updated quote token status for core spot ${index}: ${enabled ? 'enabled' : 'disabled'}`)
}

export function updateUserFreezeStatus(
    index: string | number,
    isTestnet: boolean,
    userAddress: string,
    frozen: boolean,
    logger?: Logger
) {
    const fullPath = getFullPath(index.toString(), isTestnet)

    const spot = getCoreSpotDeployment(index, isTestnet, logger)

    // Ensure blacklistUsers array exists
    if (!spot.userGenesis.blacklistUsers) {
        spot.userGenesis.blacklistUsers = []
    }

    // Normalize user address - hyperliquid indexes based on lowercase addresses
    const normalizedAddress = userAddress.toLowerCase()

    // Remove existing entry for this user
    spot.userGenesis.blacklistUsers = spot.userGenesis.blacklistUsers.filter((addr) => addr !== normalizedAddress)

    // Add user to blacklist if frozen
    if (frozen) {
        spot.userGenesis.blacklistUsers.push(normalizedAddress)
    }

    fs.writeFileSync(fullPath, JSON.stringify(spot, null, 2))
    logger?.info(
        `Updated freeze status for user ${normalizedAddress} in core spot ${index}: ${frozen ? 'frozen' : 'unfrozen'}`
    )
}

export async function getHyperEVMOAppDeployment(
    oapp_config: string,
    network: string,
    logger?: Logger
): Promise<{ contractName: string; deployment: string }> {
    if (oapp_config == undefined) {
        logger?.info(`oapp-config not supplied. prompting user for inputs`)
        throw new Error(`oapp-config not found for ${network}`)
    }

    const targetEid =
        network === 'testnet'
            ? EndpointId.HYPERLIQUID_V2_TESTNET.valueOf()
            : EndpointId.HYPERLIQUID_V2_MAINNET.valueOf()

    const lzConfigPath = path.join(process.cwd(), oapp_config)
    logger?.info(`LZ config path: ${lzConfigPath}`)

    const lzConfig = await importDefault(lzConfigPath)
    const contracts = (lzConfig as OAppOmniGraphHardhat).contracts
    const contractName = contracts.find((contract) => contract.contract.eid === targetEid)?.contract.contractName
    if (!contractName) {
        throw new Error(`HyperEVM deployment not found for ${targetEid}`)
    }

    const hardhatConfigPath = path.join(process.cwd(), 'hardhat.config.ts')
    logger?.info(`Hardhat config path: ${hardhatConfigPath}`)

    const hardhatConfig = await importDefault(hardhatConfigPath)
    const networks = (hardhatConfig as HardhatUserConfig).networks
    const networkKey = Object.keys(networks as NetworkUserConfig).find((key) => networks?.[key]?.eid === targetEid)
    if (!networkKey) {
        throw new Error(`Network not found for ${targetEid}`)
    }

    const deploymentFilePath = path.join(process.cwd(), 'deployments', networkKey, `${contractName}.json`)
    logger?.info(`HyperEVM-${network} deployment file: ${deploymentFilePath}`)

    const deploymentFile = JSON.parse(fs.readFileSync(deploymentFilePath, 'utf8'))

    return {
        contractName: contractName,
        deployment: deploymentFile,
    }
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function getERC20abi(): Promise<any> {
    const erc20Path = path.join(__dirname, '..', 'src', 'types', 'ERC20.json')
    const erc20Abi = JSON.parse(fs.readFileSync(erc20Path, 'utf8'))
    return erc20Abi['abi']
}
