const solc = require('solc')
import {
  DeployContractParams,
  DeployContractResult,
  VerifyContractParams
} from '@/lib/functions/types'
import handleImports from '@/lib/functions/deploy-contract/handle-imports'
import { getChainById, getExplorerUrl } from '@/lib/viem-utils'
import ipfsUpload from '@/lib/functions/deploy-contract/ipfs-upload'
import { Chain, Hex, createWalletClient, encodeDeployData, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { track } from '@vercel/analytics/server'

const ALCHEMY_API_KEY = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY

export default async function deployContract({
  chainId,
  contractName,
  sourceCode,
  constructorArgs
}: DeployContractParams): Promise<DeployContractResult> {
  const viemChain = getChainById(Number(chainId)) as Chain

  const fileName = contractName.replace(/[\/\\:*?"<>|.\s]+$/g, '_') + '.sol'

  // Prepare the sources object for the Solidity compiler
  const handleImportsResult = await handleImports(sourceCode)

  const sources = {
    [fileName]: {
      content: handleImportsResult?.sourceCode
    },
    ...handleImportsResult?.sources
  }
  const sourcesKeys = Object.keys(sources)

  // Loop over each source
  for (const sourceKey of sourcesKeys) {
    let sourceCode = sources[sourceKey].content

    // Find all import statements in the source code
    const importStatements = sourceCode.match(/import\s+["'][^"']+["'];/g) || []

    // Loop over each import statement
    for (const importStatement of importStatements) {
      // Extract the file name from the import statement
      const importPathMatch = importStatement.match(/["']([^"']+)["']/)

      // If no import path is found, continue to the next statement
      if (!importPathMatch) continue

      // Extract the file name from the path
      const importPath = importPathMatch[1]
      const fileName = importPath.split('/').pop() || importPath

      // Check if the file is already in the sources object
      // if (sources[fileName]) continue;

      // Replace the import statement with the new import statement
      sourceCode = sourceCode.replace(importStatement, `import "${fileName}";`)
    }

    // Update the source content in your sources object
    sources[sourceKey].content = sourceCode
  }

  // Compile the contract
  const standardJsonInput = JSON.stringify({
    language: 'Solidity',
    sources,
    settings: {
      evmVersion: 'paris',
      outputSelection: {
        '*': {
          '*': ['*']
        }
      },
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  })

  const output = JSON.parse(solc.compile(standardJsonInput))
  if (output.errors) {
    // Filter out warnings
    const errors = output.errors.filter(
      (error: { severity: string }) => error.severity === 'error'
    )
    if (errors.length > 0) {
      const error = new Error(errors[0].formattedMessage)
      throw error
    }
  }
  const contract = output.contracts[fileName]

  // Get the contract ABI and bytecode
  const abi = contract[contractName].abi
  let bytecode = contract[contractName].evm.bytecode.object
  if (!bytecode.startsWith('0x')) {
    bytecode = '0x' + bytecode
  }

  const deployerPk: Hex = `0x${process.env.DEPLOYER_PRIVATE_KEY}`
  const account = privateKeyToAccount(deployerPk)

  const alchemyHttpUrl = viemChain?.rpcUrls?.alchemy?.http[0]
    ? `${viemChain.rpcUrls.alchemy.http[0]}/${ALCHEMY_API_KEY}`
    : undefined

  const walletClient = createWalletClient({
    account,
    chain: viemChain,
    transport: http(alchemyHttpUrl)
  })

  if (!(await walletClient.getAddresses())) {
    const error = new Error(`Wallet for chain ${viemChain.name} not available`)
    console.log(error)
  }

  const deployData = encodeDeployData({
    abi: abi,
    bytecode: bytecode,
    args: constructorArgs
  })

  const deployHash = await walletClient.deployContract({
    abi: abi,
    bytecode: bytecode,
    account: account,
    args: constructorArgs
  })

  const explorerUrl = `${getExplorerUrl(viemChain)}/tx/${deployHash}`

  const ipfsCid = await ipfsUpload(
    sources,
    JSON.stringify(abi),
    bytecode,
    standardJsonInput
  )

  const ipfsUrl = `https://nftstorage.link/ipfs/${ipfsCid}`

  const encodedConstructorArgs = deployData.slice(bytecode?.length)

  const verifyContractConfig: VerifyContractParams = {
    deployHash,
    standardJsonInput,
    encodedConstructorArgs,
    fileName,
    contractName,
    viemChain
  }

  const deploymentData = {
    sourceCode,
    explorerUrl,
    ipfsUrl,
    verifyContractConfig,
    abi,
    standardJsonInput
  }

  track('deployed_contract', {
    contractName,
    explorerUrl
  })

  return deploymentData
}
