import type { Client } from '../../../clients/createClient.js'
import type { Transport } from '../../../clients/transports/createTransport.js'
import type { ErrorType } from '../../../errors/utils.js'
import type { Chain } from '../../../types/chain.js'
import type { AssetGatewayUrls } from '../../../types/ens.js'

import {
  type GetJsonImageErrorType,
  type GetMetadataAvatarUriErrorType,
  type GetNftTokenUriErrorType,
  getJsonImage,
  getMetadataAvatarUri,
  getNftTokenUri,
  type ParseAvatarUriErrorType,
  type ParseNftUriErrorType,
  parseAvatarUri,
  parseNftUri,
  type ResolveAvatarUriErrorType,
  resolveAvatarUri,
} from './utils.js'

export type ParseAvatarRecordErrorType =
  | ParseNftAvatarUriErrorType
  | ParseAvatarUriErrorType
  | ErrorType

/*
 * @description Parses an ENS avatar record.
 *
 * @example
 * parseAvatarRecord('eip155:1/erc1155:0xb32979486938aa9694bfc898f35dbed459f44424/10063')
 * 'https://ipfs.io/ipfs/QmSP4nq9fnN9dAiCj42ug9Wa79rqmQerZXZch82VqpiH7U/image.gif'
 *
 * @see https://docs.ens.domains/web/avatars
 *
 */
export async function parseAvatarRecord<chain extends Chain | undefined>(
  client: Client<Transport, chain>,
  {
    gatewayUrls,
    record,
  }: {
    gatewayUrls?: AssetGatewayUrls | undefined
    record: string
  },
): Promise<string> {
  if (/eip155:/i.test(record))
    return parseNftAvatarUri(client, { gatewayUrls, record })
  return parseAvatarUri({ uri: record, gatewayUrls })
}

type ParseNftAvatarUriErrorType =
  | ParseNftUriErrorType
  | GetNftTokenUriErrorType
  | ResolveAvatarUriErrorType
  | ParseAvatarUriErrorType
  | GetJsonImageErrorType
  | GetMetadataAvatarUriErrorType
  | ErrorType

async function parseNftAvatarUri<chain extends Chain | undefined>(
  client: Client<Transport, chain>,
  {
    gatewayUrls,
    record,
  }: {
    gatewayUrls?: AssetGatewayUrls | undefined
    record: string
  },
): Promise<string> {
  // parse NFT URI into properties
  const nft = parseNftUri(record)
  // fetch tokenURI from the NFT contract
  const nftUri = await getNftTokenUri(client, { nft })
  // resolve the URI from the fetched tokenURI
  const {
    uri: resolvedNftUri,
    isOnChain,
    isEncoded,
  } = resolveAvatarUri({ uri: nftUri, gatewayUrls })

  // if the resolved URI is on chain, return the data
  if (
    isOnChain &&
    (resolvedNftUri.includes('data:application/json;base64,') ||
      resolvedNftUri.startsWith('{'))
  ) {
    const encodedJson = isEncoded
      ? // if it is encoded, decode it
        atob(resolvedNftUri.replace('data:application/json;base64,', ''))
      : // if it isn't encoded assume it is a JSON string, but it could be anything (it will error if it is)
        resolvedNftUri

    const decoded = JSON.parse(encodedJson)
    return parseAvatarUri({ uri: getJsonImage(decoded), gatewayUrls })
  }

  let uriTokenId = nft.tokenID
  if (nft.namespace === 'erc1155')
    uriTokenId = uriTokenId.replace('0x', '').padStart(64, '0')

  return getMetadataAvatarUri({
    gatewayUrls,
    uri: resolvedNftUri.replace(/(?:0x)?{id}/, uriTokenId),
  })
}
