import React, { useMemo } from "react"
import { erc20Abi } from "viem"
import { useAccount, useWalletClient } from "wagmi"
import { Address } from "brahma-console-kit"
import { switchChain, waitForTransactionReceipt } from "@wagmi/core"
import { toast } from "react-toastify"

import {
  Button,
  ContentWrapper,
  dispatchToast,
  FlexContainer,
  GrayBoundaryBlackWrapper,
  Typography,
} from "@/components/shared/components"
import TextWithTooltip from "@/components/shared/components/TextWithTooltip"
import useAutomationAgentStore from "@/components/swap-agent/store"
import { BASE_CHAIN_ID } from "@/constants"
import useBoolean from "@/hooks/useBoolean"
import { useThemeContext } from "@/providers/themeProvider"
import {
  formatRejectMetamaskErrorMessage,
  formatUnits,
  parseUnits,
  truncateString,
} from "@/utils"
import { waitFor } from "@/components/morphoStrategy/utils"
import { AutomationAgentMetadata } from "@/components/swap-agent/types"
import { CheckCircle } from "@/components/morphoStrategy/DepositModal"
import { baseWagmiConfig as wagmiConfig } from "@/wagmi"
import { getTokenPrice } from "@/components/shared/api"
import { useIsMobile } from "@/hooks/use-is-mobile"
import useConfigStore from "@/components/shared/store"
import localStorageService from "@/components/shared/services/localStoage"
import { calculateAutomationEvery } from "@/components/swap-agent/utils"

function ReviewStepButtonsSection() {
  const { data: signer } = useWalletClient()
  const { address: eoaAddress, chainId } = useAccount()
  const { theme } = useThemeContext()

  const wrongChainId = chainId !== BASE_CHAIN_ID

  const {
    existingSafeAddress,
    feeEstimate,
    automationAgentData,
    selectedAgent,
    fetchEoaAssets,
    pollTaskStatus,
    createTransactionEntry,
    balances: accountAssets,
    preComputedConsoleAddress,
    fetchConsoleBalances,
    generateAndDeploySubAccount,
    subscribeToAutomationWithExistingConsole,
    goToNextStep,
    fetchAutomations,
    updatePermissionModalData,
    setExistingSafeAddress,
  } = useAutomationAgentStore()

  const { assets } = useConfigStore()

  const {
    amount,
    duration,
    primaryToken,
    secondaryToken,
    tradeSizeAmount: maxTradeSize,
  } = automationAgentData[selectedAgent]

  const inputToken = selectedAgent === "SURGE" ? secondaryToken : primaryToken
  const outputToken = selectedAgent === "SURGE" ? primaryToken : secondaryToken

  const { value: fundsDeposited, setValue: setFundsDeposited } =
    useBoolean(false)
  const { value: fundsDepositedLoading, setValue: setFundsDepositedLoading } =
    useBoolean(false)
  const { value: waitForDepositLoading, setValue: setWaitForDepositLoading } =
    useBoolean(false)
  const { value: consoleDeployedLoading, setValue: setConsoleDeployedLoading } =
    useBoolean(false)

  const isDepositDone = useMemo(() => {
    if (!inputToken) return false

    const feeTokenAddress = inputToken.address.toLowerCase() as Address
    const feeTokenBalance =
      accountAssets.data.find(
        (token) => token?.address.toLowerCase() === feeTokenAddress,
      )?.balanceOf?.value || BigInt(0)

    const feeTokenAmount = parseUnits(amount, inputToken?.decimals)

    return existingSafeAddress
      ? feeTokenBalance - feeTokenAmount >= BigInt(0)
      : accountAssets.data.length !== 0
  }, [accountAssets.data, amount, existingSafeAddress, inputToken])

  const isFeeGreaterThanDeposit = useMemo(() => {
    if (fundsDeposited || !feeEstimate || !inputToken) return false
    const feeTokenAmount =
      feeEstimate && amount
        ? parseUnits(amount, inputToken?.decimals)
        : BigInt(0)
    const reducedAmount = feeTokenAmount - BigInt(feeEstimate)

    return reducedAmount < 0
  }, [fundsDeposited, feeEstimate, inputToken, amount])

  const isMobile = useIsMobile()

  async function handleDepositFunds() {
    if (wrongChainId) {
      switchChain(wagmiConfig, {
        chainId: BASE_CHAIN_ID,
      })
      return
    }

    const consoleAddress = existingSafeAddress
      ? existingSafeAddress
      : preComputedConsoleAddress

    console.log("[Deposit] Step 1:  Validate inputs")

    if (!signer) {
      console.error("[Deposit] : Missing signer information for deposit")
      dispatchToast({
        id: "deposit-missing-signer",
        type: "error",
        title: "Missing Signer",
        description: {
          value: "Please check your wallet connection.",
        },
      })
      return
    }

    if (!eoaAddress) {
      console.error("[Deposit] : Missing EOA address for deposit")
      dispatchToast({
        id: "deposit-missing-eoaAddress",
        type: "error",
        title: "Missing EOA Address",
        description: {
          value: "Please check your wallet connection.",
        },
      })
      return
    }

    if (!consoleAddress) {
      console.error("[Deposit] : Missing console address for deposit")
      dispatchToast({
        id: "deposit-missing-consoleAddress",
        type: "error",
        title: "Missing Console Address",
        description: {
          value: "Please check your wallet connection.",
        },
      })
      return
    }

    if (!inputToken) {
      console.error("[Deposit] : Missing input token for deposit")
      dispatchToast({
        id: "deposit-missing-inputToken",
        type: "error",
        title: "Missing Input Token",
        description: {
          value: "Please select an input token.",
        },
      })
      return
    }

    if (!amount) {
      console.error("[Deposit] : Missing amount for deposit")
      dispatchToast({
        id: "deposit-missing-amount",
        type: "error",
        title: "Missing Amount",
        description: {
          value: "Please enter an amount.",
        },
      })
      return
    }

    const feeTokenAddress = inputToken.address.toLowerCase() as Address
    const feeTokenBalance =
      accountAssets.data.find(
        (token) => token?.address.toLowerCase() === feeTokenAddress,
      )?.balanceOf?.value || BigInt(0)

    const feeTokenAmount = parseUnits(amount, inputToken.decimals)
    const depositAmount = feeTokenAmount - feeTokenBalance

    console.log(
      `[Deposit] Fee Token Address: ${feeTokenAddress}, Balance: ${formatUnits(feeTokenBalance, inputToken.decimals)}, Required Amount: ${formatUnits(feeTokenAmount, inputToken.decimals)}, Deposit Amount: ${formatUnits(depositAmount, inputToken.decimals)}`,
    )

    if (depositAmount <= BigInt(0)) {
      console.log("[Deposit] Sufficient balance available, returning early.")
      return
    }

    try {
      setFundsDepositedLoading(true)
      // Step 2: Initiate deposit
      dispatchToast({
        id: "deposit-start",
        type: "loading",
        title: "Deposit in Progress",
        description: { value: "Initiating funds transfer. Please wait..." },
        toastOptions: { autoClose: false },
      })

      console.log("[Deposit] Step 1: Initiating ERC-20 transfer...")
      const txnHash = await signer.writeContract({
        abi: erc20Abi,
        address: feeTokenAddress,
        functionName: "transfer",
        args: [consoleAddress, depositAmount],
      })

      // Step 3: Wait for transaction completion
      console.log("[Deposit] Step 3: Waiting for transaction receipt...")
      await waitForTransactionReceipt(wagmiConfig, { hash: txnHash })

      // Step 4: Funds detection loop
      console.log("[Deposit] Step 4: Starting funds detection loop...")
      dispatchToast({
        id: "deposit-detection",
        type: "loading",
        title: "Verifying Deposit",
        description: { value: "Confirming funds in console account..." },
        toastOptions: {
          autoClose: false,
        },
      })

      let detectionAttempt = 1
      let areFundsDetected = false
      while (!areFundsDetected) {
        console.log(`[Deposit] Detection attempt #${detectionAttempt++}`)
        areFundsDetected = await fetchConsoleBalances(
          [inputToken],
          consoleAddress,
          inputToken.address,
        )

        if (!areFundsDetected) {
          console.log(
            "[Deposit] Funds not yet detected. Retrying in 3 seconds...",
          )
          await waitFor(3000)
        }
      }

      // Step 5: Success
      console.log("[Deposit] Step 5: Funds successfully detected!")
      setFundsDeposited(true)
      dispatchToast({
        id: "funds-deposited",
        title: "Deposit Successful",
        type: "success",
        description: {
          value: `Funds deposited to console account! Transaction hash: ${truncateString(txnHash, 8)}`,
        },
      })
    } catch (err: any) {
      console.error("[Deposit] Error occurred:", err)
      dispatchToast({
        id: "deposit-error",
        title: "Deposit Failed",
        type: "error",
        description: {
          value:
            formatRejectMetamaskErrorMessage(err) ||
            err?.shortMessage ||
            err?.message ||
            "Failed to complete deposit transaction",
        },
      })
      setFundsDeposited(false)
    } finally {
      toast.dismiss("deposit-start")
      toast.dismiss("deposit-detection")
      setFundsDepositedLoading(false)
      setWaitForDepositLoading(true)

      console.log("[Deposit] Refreshing EOA assets...")
      eoaAddress && fetchEoaAssets(eoaAddress, assets)

      setTimeout(() => {
        setWaitForDepositLoading(false)
        console.log("[Deposit] Post-deposit cleanup completed")
      }, 5000)
    }
  }

  async function handleDeployConsoleForExistingConsole() {
    console.log("[Deploy Existing Console] Step 1: Validate inputs")

    if (
      !existingSafeAddress ||
      !eoaAddress ||
      !signer ||
      !inputToken ||
      !outputToken
    ) {
      console.error("[Deploy Existing Console] : Missing required information")
      dispatchToast({
        id: "console-deploy-error",
        title: "Error deploying console",
        type: "error",
        description: {
          value: "Missing required information for account deployment",
        },
      })
      return
    }

    console.log(
      "[Deploy Existing Console] Step 2: Prepare token inputs and limits",
    )
    const tokenInputs = {
      [inputToken.address]: parseUnits(amount, inputToken.decimals).toString(),
    }

    const tokenLimits = {
      [inputToken.address]: maxTradeSize.toString(),
      [outputToken.address]: Number.MAX_SAFE_INTEGER.toString(),
    }

    console.log("[Deploy Existing Console] Step 3: Fetch token price")
    const price = await getTokenPrice(inputToken.address, "base")

    const parsedAmount = parseFloat(amount)
    const twoPercentOfAmount = parsedAmount * 0.02
    const twoDollarEquivalentAmount = price.data
      ? 2 / price.data
      : twoPercentOfAmount
    const minTradeSize = `${Math.max(twoDollarEquivalentAmount, twoPercentOfAmount)}`

    const durationInSeconds = duration * 3_600
    // const medianTradeSize = (maxTradeSize + Number(minTradeSize)) / 2
    // // const formattedDuration = Math.floor(
    // //   durationInSeconds / (Number(amount) / medianTradeSize),
    // )
    const formattedDuration = calculateAutomationEvery(
      amount,
      price.data,
      duration,
      maxTradeSize,
    )

    console.log("[Deploy Existing Console] Step 4: Prepare metadata")
    const metadata: AutomationAgentMetadata = {
      buyToken: outputToken.address,
      chainId: BASE_CHAIN_ID,
      every: formattedDuration.toString(),
      consoleAddress: existingSafeAddress,
      duration: durationInSeconds.toString(),
      limitMarketCap: 0,
      limitThresholdPrice: 0,
      orderType: selectedAgent === "SURGE" ? "BUY" : "SELL",
      perIterationAmountMax: parseUnits(
        maxTradeSize.toString(),
        inputToken.decimals,
      ).toString(),
      perIterationAmountMin: parseUnits(
        minTradeSize,
        inputToken.decimals,
      ).toString(),
      sellToken: inputToken.address,
      ownerAddress: eoaAddress,
      totalAmountIn: parseUnits(amount, inputToken.decimals).toString(),
    }

    console.log("[Deploy Existing Console] Step 5: Subscribe to automation")
    await subscribeToAutomationWithExistingConsole(
      eoaAddress,
      signer,
      BASE_CHAIN_ID,
      tokenInputs,
      tokenLimits,
      metadata,
      existingSafeAddress,
      setConsoleDeployedLoading,
    )
  }

  async function handleDeployConsole() {
    console.log("[Deploy Console] Step 1: Validate inputs")

    if (
      !preComputedConsoleAddress ||
      !feeEstimate ||
      !eoaAddress ||
      !inputToken ||
      !outputToken
    ) {
      console.error("[Deploy Console] : Missing required information")
      dispatchToast({
        id: "console-deploy-error",
        title: "Error deploying console",
        type: "error",
        description: {
          value: "Missing required information for account deployment",
        },
      })
      return
    }

    const feeTokenAddress = inputToken.address.toLowerCase() as Address
    const feeTokenBalance =
      accountAssets.data.find(
        (token) => token?.address.toLowerCase() === feeTokenAddress,
      )?.balanceOf?.value || BigInt(0)

    if (feeTokenBalance < BigInt(feeEstimate)) {
      console.error("[Deploy Console] : Insufficient balance for the fee token")
      dispatchToast({
        id: "console-deploy-error",
        title: "Error deploying console",
        type: "error",
        description: {
          value: "Insufficient balance for the fee token",
        },
      })
      return
    }

    if (
      !fundsDeposited &&
      accountAssets.data.some((token) => {
        const tokenAmount = token?.balanceOf?.value || BigInt(0)
        return tokenAmount <= 0
      })
    ) {
      console.error("[Deploy Console] : Token amount cannot be zero")
      dispatchToast({
        id: "console-deploy-error",
        title: "Error deploying account",
        type: "error",
        description: {
          value: "Token amount cannot be zero",
        },
      })
      return
    }

    console.log("[Deploy Console] Step 2: Set loading state and prepare data")
    setConsoleDeployedLoading(true)
    updatePermissionModalData({ isShowingModal: true, state: "PENDING" })

    try {
      const tokens = accountAssets.data.map(
        (asset) => asset?.address as Address,
      )

      const amounts = accountAssets.data.map((token) => {
        const tokenAmount = token.balanceOf?.value || BigInt(0)
        const adjustedAmount =
          token?.address.toLowerCase() === feeTokenAddress
            ? tokenAmount - BigInt(feeEstimate)
            : tokenAmount
        return adjustedAmount.toString()
      })

      const tokenInputs = {
        [inputToken.address]: parseUnits(
          amount,
          inputToken.decimals,
        ).toString(),
      }

      const tokenLimits = {
        [inputToken.address]: maxTradeSize.toString(),
        [outputToken.address]: Number.MAX_SAFE_INTEGER.toString(),
      }

      console.log("[Deploy Console] Step 3: Fetch token price")
      const price = await getTokenPrice(inputToken.address, "base")

      const parsedAmount = parseFloat(amount)
      const twoPercentOfAmount = parsedAmount * 0.02
      const twoDollarEquivalentAmount = price.data
        ? 2 / price.data
        : twoPercentOfAmount
      const minTradeSize = `${Math.max(twoDollarEquivalentAmount, twoPercentOfAmount)}`

      const durationInSeconds = duration * 3_600
      const medianTradeSize = (maxTradeSize + Number(minTradeSize)) / 2
      const formattedDuration = Math.floor(
        durationInSeconds / (Number(amount) / medianTradeSize),
      )

      console.log("[Deploy Console] Step 4: Prepare metadata")
      const metadata: AutomationAgentMetadata = {
        buyToken: outputToken.address,
        chainId: BASE_CHAIN_ID,
        every: formattedDuration.toString(),
        consoleAddress: preComputedConsoleAddress,
        duration: durationInSeconds.toString(),
        limitMarketCap: 0,
        limitThresholdPrice: 0,
        orderType: selectedAgent === "SURGE" ? "BUY" : "SELL",
        perIterationAmountMax: parseUnits(
          maxTradeSize.toString(),
          inputToken.decimals,
        ).toString(),
        perIterationAmountMin: parseUnits(
          minTradeSize,
          inputToken.decimals,
        ).toString(),
        sellToken: inputToken.address,
        ownerAddress: eoaAddress,
        totalAmountIn: parseUnits(amount, inputToken.decimals).toString(),
      }

      console.log("[Deploy Console] Step 5: Generate and deploy sub-account")
      const deploymentStatus = await generateAndDeploySubAccount(
        eoaAddress,
        BASE_CHAIN_ID,
        feeTokenAddress,
        feeEstimate,
        tokens,
        amounts,
        tokenInputs,
        tokenLimits,
        metadata,
      )

      console.log("deploymentStatus", deploymentStatus)

      if (!deploymentStatus?.taskId) throw new Error("Failed to deploy agent")

      dispatchToast({
        id: "deployment-progress",
        title: "Setup in Progress",
        description: {
          value: "Fetching deployment progress...",
        },
        type: "loading",
        toastOptions: {
          autoClose: false,
        },
      })

      console.log("[Deploy Console] Step 6: Poll task status")
      const txnHash = await pollTaskStatus(deploymentStatus.taskId)
      if (!txnHash) {
        throw new Error("Task deployment failed")
      }

      toast.dismiss("deployment-progress")
      console.log("waiting for waitForTransactionReceipt", txnHash)
      await waitForTransactionReceipt(wagmiConfig, {
        hash: txnHash as Address,
      })

      console.log("indexing")
      await createTransactionEntry(txnHash, BASE_CHAIN_ID)

      console.log("indexerResponse")

      dispatchToast({
        id: "deploy-automation",
        title: "Setup Complete",
        description: {
          value: "Agent account deployed successfully!",
        },
        type: "success",
      })

      console.log("polled")

      await new Promise((resolve) => setTimeout(resolve, 3000))

      console.log(
        "Storing Pre-compute Console Address:",
        preComputedConsoleAddress,
      )
      localStorageService.setDeployedSwapAgentConsole(preComputedConsoleAddress) // Store in local storage
      setExistingSafeAddress(preComputedConsoleAddress) // Update state with the found address

      updatePermissionModalData({ isShowingModal: true, state: "SUCCESS" })
      goToNextStep()
      fetchAutomations(eoaAddress)
    } catch (error: any) {
      console.error("Error deploying Brahma Account:", error)
      toast.dismiss("deployment-progress")
      dispatchToast({
        id: "deploy-automation",
        title: "Setup Failed",
        description: {
          value:
            error instanceof Error
              ? error.message
              : "Failed to deploy agent account",
        },
        type: "error",
      })
      updatePermissionModalData({ isShowingModal: false })
    } finally {
      setConsoleDeployedLoading(false)
    }
  }

  return (
    <GrayBoundaryBlackWrapper padding="0.4rem">
      <ContentWrapper padding="1.6rem">
        <FlexContainer justifyContent="space-between" alignItems="center">
          <FlexContainer gap={0.8} alignItems="center">
            <CheckCircle serialNumber={isDepositDone ? undefined : 1} />
            <Typography color={theme.colors.gray100} type="TITLE_XS">
              Deposit Funds
            </Typography>
          </FlexContainer>

          <Button
            onClick={handleDepositFunds}
            disabled={
              !wrongChainId &&
              (isDepositDone ||
                isFeeGreaterThanDeposit ||
                fundsDepositedLoading ||
                waitForDepositLoading)
            }
            buttonType="white"
            buttonSize={isMobile ? "S" : "M"}
          >
            {wrongChainId
              ? "Switch to Base"
              : fundsDepositedLoading
                ? "Depositing"
                : waitForDepositLoading && !isDepositDone
                  ? "Waiting for funds"
                  : "Deposit"}
          </Button>
        </FlexContainer>
      </ContentWrapper>

      <ContentWrapper padding="1.6rem">
        <FlexContainer justifyContent="space-between" alignItems="center">
          <FlexContainer gap={0.8} alignItems="center">
            <CheckCircle serialNumber={2} />
            <Typography color={theme.colors.gray100} type="TITLE_XS">
              Activate Agent
            </Typography>
          </FlexContainer>

          <Button
            onClick={async () => {
              if (existingSafeAddress)
                await handleDeployConsoleForExistingConsole()
              else await handleDeployConsole()
            }}
            buttonType="white"
            disabled={!isDepositDone || consoleDeployedLoading}
            buttonSize={isMobile ? "S" : "M"}
          >
            {consoleDeployedLoading ? "Activating" : "Activate"}
          </Button>
        </FlexContainer>
      </ContentWrapper>

      <FlexContainer padding="0.8rem" width={100} justifyContent="center">
        <TextWithTooltip
          text="Both steps are mandatory to start the Agent"
          tooltipText="Complete both Deposit and Activate to start the Agent. If you finish only the first step and leave the page, you can resume exactly where you left off in your next session."
          color={theme.colors.gray100}
          type="BODY_MEDIUM_XS"
        />
      </FlexContainer>
    </GrayBoundaryBlackWrapper>
  )
}

export default ReviewStepButtonsSection
