import { HyperliquidClient, IHyperliquidSigner } from '../signer'
import {
    BaseInfoRequest,
    SpotMeta,
    SpotInfo,
    SpotDeployStates,
    SpotMetaUniverse,
    SpotPair,
    SpotPairsWithMetadata,
    SpotPairDeployAuctionStatus,
} from '../types'
import { HYPE_INDEX } from '../types/constants'

export async function getSpotMeta(
    signer: IHyperliquidSigner | null,
    isTestnet: boolean,
    logLevel: string,
    tokenIndex: string
) {
    const action: BaseInfoRequest = {
        type: 'spotMeta',
    }

    const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel)
    const response = await hyperliquidClient.submitHyperliquidAction('/info', signer, action)
    const tokens = (response as SpotMeta).tokens
    const token = tokens.find((token) => token.index === parseInt(tokenIndex))
    if (!token) {
        throw new Error(`Token ${tokenIndex} not found`)
    }
    return token
}

export async function getHipTokenInfo(
    signer: IHyperliquidSigner | null,
    isTestnet: boolean,
    logLevel: string,
    tokenAddress: string
) {
    const action: BaseInfoRequest = {
        type: 'tokenDetails',
        tokenId: tokenAddress,
    }

    const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel)
    const response = await hyperliquidClient.submitHyperliquidAction('/info', signer, action)
    const token = response as SpotInfo

    return token
}

export async function getSpotDeployState(deployerAddres: string, isTestnet: boolean, logLevel: string) {
    const action: BaseInfoRequest = {
        type: 'spotDeployState',
        user: deployerAddres,
    }

    const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel)
    const response = await hyperliquidClient.submitHyperliquidAction('/info', null, action)
    const deployState = response as SpotDeployStates
    return deployState
}

/**
 * Gets all spot trading pairs for a given coreSpot token index.
 * This function fetches the complete spotMeta universe and filters for pairs
 * that include the specified token index.
 *
 * @param isTestnet Whether to query testnet or mainnet
 * @param tokenIndex The token index to find pairs for
 * @param logLevel Logging level for the client
 * @returns Array of spot pairs that include the specified token
 */
export async function getSpotPairs(isTestnet: boolean, tokenIndex: number, logLevel: string): Promise<SpotPair[]> {
    const action: BaseInfoRequest = {
        type: 'spotMeta',
    }

    const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel)
    const response = await hyperliquidClient.submitHyperliquidAction('/info', null, action)
    const spotMetaUniverse = response as SpotMetaUniverse

    // Filter pairs that include the specified token index
    const filteredPairs = spotMetaUniverse.universe.filter((pair) => pair.tokens.includes(tokenIndex))

    return filteredPairs
}

/**
 * Gets all spot trading pairs for a given coreSpot token index along with token metadata.
 * This function fetches both the pairs and token metadata for name resolution.
 *
 * @param isTestnet Whether to query testnet or mainnet
 * @param tokenIndex The token index to find pairs for
 * @param logLevel Logging level for the client
 * @returns Object containing filtered pairs and token metadata
 */
export async function getSpotPairsWithMetadata(
    isTestnet: boolean,
    tokenIndex: number,
    logLevel: string
): Promise<SpotPairsWithMetadata> {
    const action: BaseInfoRequest = {
        type: 'spotMeta',
    }

    const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel)
    const response = await hyperliquidClient.submitHyperliquidAction('/info', null, action)
    const spotMetaUniverse = response as SpotMetaUniverse

    // Filter pairs that include the specified token index
    const filteredPairs = spotMetaUniverse.universe.filter((pair) => pair.tokens.includes(tokenIndex))

    return {
        pairs: filteredPairs,
        tokens: spotMetaUniverse.tokens,
    }
}

/**
 * Gets the current spot pair deploy auction status.
 * This shows information about the ongoing auction for spot pair deployments.
 *
 * @param isTestnet Whether to query testnet or mainnet
 * @param logLevel Logging level for the client
 * @returns Current auction status information
 */
export async function getSpotPairDeployAuctionStatus(
    isTestnet: boolean,
    logLevel: string
): Promise<SpotPairDeployAuctionStatus> {
    const action: BaseInfoRequest = {
        type: 'spotPairDeployAuctionStatus',
    }

    const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel)
    const response = await hyperliquidClient.submitHyperliquidAction('/info', null, action)
    return response as SpotPairDeployAuctionStatus
}

/**
 * Gets the quote tokens that a given core spot token is already paired with.
 * This is useful to check what trading pairs already exist before deploying new ones.
 *
 * @param isTestnet Whether to query testnet or mainnet
 * @param tokenIndex The token index to check existing pairs for
 * @param logLevel Logging level for the client
 * @returns Array of quote token indices that the token is already paired with
 */
export async function getExistingQuoteTokens(
    isTestnet: boolean,
    tokenIndex: number,
    logLevel: string
): Promise<number[]> {
    const pairs = await getSpotPairs(isTestnet, tokenIndex, logLevel)

    // Extract the quote tokens (other token in each pair)
    const quoteTokens = pairs
        .map((pair) => {
            // Find the token that's NOT the input tokenIndex
            return pair.tokens.find((token) => token !== tokenIndex)
        })
        .filter((token): token is number => token !== undefined)

    return [...new Set(quoteTokens)] // Remove duplicates
}

/**
 * Checks if a token is a quote asset by looking at HYPE trading pairs.
 * When any core spot is promoted to a quote asset (fee token), the Hyperliquid protocol
 * automatically deploys a new spot market for HYPE/QUOTE_ASSET.
 *
 * @param isTestnet Whether to query testnet or mainnet
 * @param tokenIndex The token index to check (optional - if not provided, returns all quote assets)
 * @param logLevel Logging level for the client
 * @returns Object containing isQuoteAsset boolean and tokenName string
 */
/**
 * Gets pending spot pairs that exist in spotMetaAndAssetCtxs but NOT in the universe.
 * These are spots that have been registered via registerSpot but not yet finalized.
 *
 * IMPORTANT: The Hyperliquid API does not provide token composition info for pending spots,
 * so this function returns ALL pending spots network-wide, not filtered by token.
 * Users should use the --spot-index flag to directly specify which spot to finalize.
 *
 * @param isTestnet Whether to query testnet or mainnet
 * @param logLevel Logging level for the client
 * @returns Array of pending spot indices and their details (network-wide, not token-specific)
 */
export async function getPendingSpotPairs(
    isTestnet: boolean,
    logLevel: string
): Promise<Array<{ spotIndex: number; coin: string; markPx: string }>> {
    const hyperliquidClient = new HyperliquidClient(isTestnet, logLevel)

    // Get spotMetaAndAssetCtxs which includes pending spots
    const assetCtxsAction: BaseInfoRequest = {
        type: 'spotMetaAndAssetCtxs',
    }
    const assetCtxsResponse = (await hyperliquidClient.submitHyperliquidAction('/info', null, assetCtxsAction)) as [
        SpotMetaUniverse,
        Array<{ coin: string; markPx: string; circulatingSupply: string; totalSupply: string }>,
    ]

    const universe = assetCtxsResponse[0].universe
    const assetCtxs = assetCtxsResponse[1]

    // Get spot indices that are in the universe (live)
    const liveSpotIndices = new Set(universe.map((pair) => pair.index))

    // Find spots in assetCtxs that are NOT in the universe (pending)
    const pendingSpots: Array<{ spotIndex: number; coin: string; markPx: string }> = []

    for (const ctx of assetCtxs) {
        // Extract spot index from coin name (e.g., "@1421" -> 1421)
        const match = ctx.coin.match(/^@(\d+)$/)
        if (match && match[1]) {
            const spotIndex = parseInt(match[1])
            if (!liveSpotIndices.has(spotIndex)) {
                pendingSpots.push({
                    spotIndex,
                    coin: ctx.coin,
                    markPx: ctx.markPx,
                })
            }
        }
    }

    return pendingSpots
}

export async function isQuoteAsset(
    isTestnet: boolean,
    tokenIndex: number | null,
    logLevel: string
): Promise<{ isQuoteAsset: boolean; tokenName: string; allQuoteAssets?: Array<{ index: number; name: string }> }> {
    // Get HYPE token index based on network
    const hypeTokenIndex = isTestnet ? HYPE_INDEX.TESTNET : HYPE_INDEX.MAINNET

    // Get all HYPE trading pairs with metadata
    const { pairs, tokens } = await getSpotPairsWithMetadata(isTestnet, hypeTokenIndex, logLevel)

    // Extract all quote assets paired with HYPE
    const quoteAssets = pairs
        .map((pair) => {
            // Find the token that's NOT HYPE
            const quoteTokenIndex = pair.tokens.find((token) => token !== hypeTokenIndex)
            if (quoteTokenIndex === undefined) {
                return null
            }

            // Find the token metadata
            const tokenMetadata = tokens.find((token) => token.index === quoteTokenIndex)
            return {
                index: quoteTokenIndex,
                name: tokenMetadata?.name || `Token-${quoteTokenIndex}`,
            }
        })
        .filter((asset): asset is { index: number; name: string } => asset !== null)

    // If no tokenIndex provided, return all quote assets
    if (tokenIndex === null) {
        return {
            isQuoteAsset: false,
            tokenName: '',
            allQuoteAssets: quoteAssets,
        }
    }

    // Check if the provided tokenIndex is in the list of quote assets
    const matchedAsset = quoteAssets.find((asset) => asset.index === tokenIndex)

    return {
        isQuoteAsset: matchedAsset !== undefined,
        tokenName: matchedAsset?.name || '',
    }
}
