// type useMorphoStrategyProps = {}

import { useEffect, useMemo, useState } from "react"
import { createPublicClient, encodePacked, erc20Abi, http } from "viem"
import { base } from "viem/chains"
import { useAccount, useWalletClient } from "wagmi"
import Safe from "@safe-global/protocol-kit"

import assetsList from "@/baseNew.json"
import withdrawAbi from "@/components/shared/abi/withdrawContractAbi.json"
import { callIndexer, withdrawMorphoAmount } from "@/components/shared/api"
import { dispatchToast } from "@/components/shared/components"
import { MORPHO_STRATEGY_TOKENS } from "@/components/shared/constants"
import localStorageService from "@/components/shared/services/localStoage"
import { Address } from "@/components/shared/types"
import useBoolean from "@/hooks/useBoolean"
import usePolling from "@/hooks/usePolling"
import { SupportedChainIds, TAsset } from "@/types"
import {
  formatRejectMetamaskErrorMessage,
  formatUnits,
  isValidEthereumAddress,
  parseUnits,
} from "@/utils"

import { frequencyOptions } from "../constants"
import useStore from "../store"
import { MorphoTokensOptions, Token } from "../types"
import { waitFor } from "../utils"

const publicClient = createPublicClient({
  chain: base,
  transport: http(
    "https://base-mainnet.g.alchemy.com/v2/q2VDjsGh2h0P6WZSXUq2eTw7y0_Ffkdq",
  ),
})

export const ETHEREUM_ADDRESS =
  "0x0000000000000000000000000000000000000000" as Address

const TABS_TITLE = [
  {
    title: "Deposit",
  },
  {
    title: "Withdraw",
  },
]

export default function useMorphoStrategy(token: MorphoTokensOptions) {
  const { data: signer } = useWalletClient()
  const { address: eoaAddress, chainId } = useAccount()
  const [activeTab, setActiveTab] = useState(TABS_TITLE[0])
  const [tokenAmount, setTokenAmount] = useState<string>("")
  const [preferredVaults, setPreferredVaults] = useState<Address[]>([])
  const [selectedFrequency, setSelectedFrequency] = useState<string>(() => {
    return frequencyOptions[0].value
  })
  const [isAiPromptValueSet, setIsAiPromptValuesSet] = useState(false)

  const [withdrawModalData, setWithdrawModalData] = useState<{
    isModalOpen: boolean
    withdrawModalState: "IDLE" | "LOADING" | "SUCCESS"
    sucessState: null | { asset: TAsset; amount: string; txHash: string }
  }>({
    isModalOpen: false,
    withdrawModalState: "IDLE",
    sucessState: null,
  })

  const closeWithdrawModal = () => {
    setWithdrawModalData({
      isModalOpen: false,
      withdrawModalState: "IDLE",
      sucessState: null,
    })
  }

  const openWithdrawModal = () => {
    setWithdrawModalData({
      isModalOpen: true,
      withdrawModalState: "IDLE",
      sucessState: null,
    })
  }

  const setWithdrawModalState = (state: "IDLE" | "LOADING" | "SUCCESS") => {
    setWithdrawModalData((prev) => ({
      ...prev,
      withdrawModalState: state,
    }))
  }

  const setWithdrawSuccessState = (
    asset: TAsset,
    amount: string,
    txHash: string,
  ) => {
    setWithdrawModalData((prev) => ({
      ...prev,
      sucessState: {
        asset,
        amount,
        txHash,
      },
    }))
  }

  // const {
  //   value: isWithdrawModalOpen,
  //   setFalse: closeWithdrawModal,
  //   setTrue: openWithdrawModal,
  // } = useBoolean()

  const tokenConfig = MORPHO_STRATEGY_TOKENS[token]
  //  const {is}= useBoolean()

  const {
    eoaBalances: eoaAssets,
    balances: accountAssets,
    feeEstimate,
    deploymentStatus,
    preComputedConsoleAddress,
    fetchEoaAssets,
    fetchDeploymentStatus,
    generateAndDeploySubAccount,
    fetchPreComputedConsoleAddress,
    fetchPreComputedConsoleBalances,
    userPositions,
    setPageSpinnerState,
    resetUserPostion,
    paramsDetectedInAiRes,
    setParamsDetectedInAiRes,
    isDepositModalOpen,
    closeDepositModal,
    openDepositModal,
    // resetPreComputedConsoleAddress,
  } = useStore()

  const [feeToken, setFeeToken] = useState<Token | null>({
    amount: "",
    asset: tokenConfig,
  })

  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 tokenInDepositedBalances =
    tokenConfig &&
    accountAssets.data.find(
      (token) =>
        token.address.toLowerCase() === tokenConfig.address.toLowerCase(),
    )

  const alreadyDepositedValue =
    tokenInDepositedBalances?.balanceOf?.value || BigInt(0)

  const isTokenAlreadyDeposited = alreadyDepositedValue !== BigInt(0)

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

    return reducedAmount < 0
  }, [fundsDeposited, feeEstimate, tokenConfig, feeToken, tokenAmount])

  async function handleDepositFunds() {
    if (!signer || !eoaAddress || !preComputedConsoleAddress || !tokenConfig) {
      console.error("Missing required information for deposit")
      return
    }

    setFundsDepositedLoading(true)
    try {
      await signer.writeContract({
        abi: erc20Abi,
        address: tokenConfig?.address as Address,
        functionName: "transfer",
        args: [
          preComputedConsoleAddress as Address,
          parseUnits(tokenAmount, tokenConfig?.decimals),
        ],
      })

      let areFundsDetected = false
      while (!areFundsDetected) {
        areFundsDetected = await fetchPreComputedConsoleBalances(
          [tokenConfig],
          tokenConfig.address,
        )
        console.log("polling funds", areFundsDetected)

        await waitFor(3000)
      }

      setFundsDeposited(true) // Moved here to ensure all deposits succeed
      dispatchToast({
        id: "funds-deposited",
        title: "Funds Deposited",
        type: "success",
        description: {
          value: "Funds have been deposited to the pre-computed account",
        },
      })
    } catch (err: any) {
      console.log({ err })
      setFundsDeposited(false)
      dispatchToast({
        id: "funds-error",
        title:
          formatRejectMetamaskErrorMessage(err) || "Error depositing funds",
        type: "error",
        description: {
          value: "An error occurred while depositing funds",
        },
      })
    } finally {
      setFundsDepositedLoading(false)
      setWaitForDepositLoading(true)
      eoaAddress && fetchEoaAssets(eoaAddress, assets)
      setTimeout(() => {
        setWaitForDepositLoading(false)
      }, 5000)
    }
  }

  async function handleDeployConsole() {
    if (
      !preComputedConsoleAddress ||
      !tokenConfig ||
      !feeEstimate ||
      !eoaAddress ||
      !chainId
    ) {
      dispatchToast({
        id: "console-deploy-error",
        title: "Error deploying console",
        type: "error",
        description: {
          value: "Missing required information for account deployment",
        },
      })
      return
    }

    // Check if the account assets have a balance greater than the feeEstimate for the fee token
    const feeTokenAddress = tokenConfig.address.toLowerCase() as Address
    const feeTokenBalance =
      accountAssets.data.find(
        (token) => token?.address.toLowerCase() === feeTokenAddress,
      )?.balanceOf?.value || BigInt(0)

    if (feeTokenBalance < BigInt(feeEstimate)) {
      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
      })
    ) {
      dispatchToast({
        id: "console-deploy-error",
        title: "Error deploying account",
        type: "error",
        description: {
          value: "Token amount cannot be zero",
        },
      })
      return
    }

    const tokens = accountAssets.data.map((asset) => asset?.address as Address)
    const amounts = accountAssets.data.map((token) => {
      const tokenAmount = token.balanceOf?.value || BigInt(0)
      // Reduce the amount by feeEstimate if the token address matches feeTokenAddress
      const adjustedAmount =
        token?.address.toLowerCase() === feeTokenAddress
          ? tokenAmount - BigInt(feeEstimate)
          : tokenAmount
      return adjustedAmount.toString()
    })

    const tokenAmountForLimits =
      formatUnits(alreadyDepositedValue.toString(), tokenConfig.decimals) ||
      tokenAmount

    setConsoleDeployedLoading(true)
    try {
      const tokenInputs = {
        [tokenConfig.address]: parseUnits(
          tokenAmountForLimits.toString(),
          tokenConfig.decimals,
        ).toString(),
      }

      const tokenLimit = {
        [tokenConfig.address]: (
          parseFloat(tokenAmountForLimits) * 2
        ).toString(),
      }

      await generateAndDeploySubAccount(
        eoaAddress,
        chainId as SupportedChainIds,
        feeTokenAddress,
        feeEstimate,
        tokens,
        amounts,
        Number(selectedFrequency),
        tokenInputs,
        tokenLimit,
        preferredVaults,
      )
    } catch (err: any) {
      console.error("Error deploying Brahma Account:", err)
      dispatchToast({
        id: "deploy-error",
        title: "Error deploying Account",
        type: "error",
        description: {
          value: "An error occurred during Brahma Account deployment",
        },
      })
    } finally {
      setConsoleDeployedLoading(false)
      closeDepositModal()
    }
  }

  const assets: TAsset[] = useMemo(() => {
    return assetsList.map((cAsset) => ({
      ...cAsset,
      chainId: cAsset.chainId as SupportedChainIds,
      address: cAsset.address as Address,
      verified: cAsset.isVerified,
      actions: [],
      value: "0",
      prices: { default: 0 },
    }))
  }, [])

  const getMaxTokenBalanceAvailable: bigint =
    eoaAssets.data.find(
      (asset) =>
        asset.address.toLowerCase() ===
        (tokenConfig?.address || "").toLowerCase(),
    )?.balanceOf?.value || BigInt(0)

  const withdrawAmount = async () => {
    if (!eoaAddress || !signer) return

    try {
      setWithdrawModalState("LOADING")
      // generate txns
      const userPositionTokenData = userPositions[token].data[0].tokenData

      const consoleAddress = userPositions[token].consoleAddress
      const subaccountAddress = userPositions[token].data[0].subAccountAddress
      const vaultAddress =
        userPositions[token].data[0].vaultPositionData?.vault.address || null

      if (!vaultAddress) {
        dispatchToast({
          id: "error",
          title:
            "Opening position still in progress, please refresh and try withdraw in a minute",
        })
        return
      }
      console.log({ consoleAddress, subaccountAddress, vaultAddress })

      const txns = await withdrawMorphoAmount({
        consoleAddress,
        eoa: eoaAddress,
        subaccountAddress,
        vaultAddress: vaultAddress,
        baseAsset: userPositionTokenData.asset.address,
      })

      console.log({ txns })

      // generate safe txn obj
      const safe = await Safe.init({
        provider:
          "https://base-mainnet.g.alchemy.com/v2/q2VDjsGh2h0P6WZSXUq2eTw7y0_Ffkdq",
        safeAddress: consoleAddress,
      })

      const transaction = await safe.createTransaction({
        transactions: txns.transactions,
        onlyCalls: false,
      })

      console.log({ transaction })
      // make a call to execTransaction

      const {
        baseGas,
        data,
        gasPrice,
        gasToken,
        nonce,
        operation,
        refundReceiver,
        safeTxGas,
        to,
        value,
      } = transaction.data

      const signature = encodePacked(
        ["bytes12", "address", "bytes32", "bytes1"],
        [
          "0x000000000000000000000000",
          eoaAddress,
          "0x0000000000000000000000000000000000000000000000000000000000000000",
          "0x01",
        ],
      )

      console.log({
        signature,
        consoleAddress,
        eoaAddress,
        to,
        value,
        data,
        operation,
        safeTxGas,
        baseGas,
        gasPrice,
        gasToken,
        refundReceiver,
      })

      const writeResponse = await signer.writeContract({
        address: consoleAddress,
        abi: withdrawAbi,
        functionName: "execTransaction",
        account: eoaAddress,
        gas: BigInt(2000000),
        args: [
          to,
          value,
          data,
          operation,
          safeTxGas,
          baseGas,
          gasPrice,
          gasToken,
          refundReceiver,
          signature,
        ],
      })

      console.log({ writeResponse })

      const transactionStatus = await publicClient.waitForTransactionReceipt({
        hash: writeResponse,
      })

      console.log({ transactionStatus })

      await callIndexer(writeResponse)
      resetUserPostion(token)

      setWithdrawSuccessState(
        userPositionTokenData.asset,
        userPositionTokenData.amount,
        transactionStatus.transactionHash,
      )
      setWithdrawModalState("SUCCESS")
    } catch (err) {
      console.error("[Error at withdrawAmount]", err)
      setWithdrawModalState("IDLE")
      dispatchToast({
        id: "error",
        type: "error",
        title: "Something went wrong",
        description: { value: "Please refresh page and try again" },
      })
    }
  }

  useEffect(() => {
    eoaAddress && fetchEoaAssets(eoaAddress, assets)
  }, [assets, eoaAddress, fetchEoaAssets])

  usePolling(() => {
    eoaAddress && fetchEoaAssets(eoaAddress, assets)
  }, 5000)

  usePolling(() => {
    if (!deploymentStatus || !deploymentStatus.taskId || !eoaAddress) return
    if (
      deploymentStatus.status === "successful" ||
      deploymentStatus.status === "failed" ||
      deploymentStatus.status === "cancelled"
    )
      return
    fetchDeploymentStatus(deploymentStatus.taskId, token, eoaAddress)
  }, 5000)

  useEffect(() => {
    if (!eoaAddress || !tokenConfig || !chainId) return

    fetchPreComputedConsoleAddress(
      eoaAddress,
      chainId as SupportedChainIds,
      tokenConfig.address,
    )
  }, [
    chainId,
    eoaAddress,
    tokenConfig?.address,
    fetchPreComputedConsoleAddress,
    tokenConfig,
  ])

  // useEffect(() => {
  //   resetUserPostion("usdc");
  //   resetUserPostion("weth");
  //   resetPreComputedConsoleAddress();
  // }, [eoaAddress]);

  useEffect(() => {
    if (!preComputedConsoleAddress || !tokenConfig) return
    fetchPreComputedConsoleBalances([tokenConfig])
  }, [fetchPreComputedConsoleBalances, preComputedConsoleAddress, tokenConfig])

  useEffect(() => {
    if (!feeToken?.asset) {
      setFundsDeposited(false)
      return
    }

    const preComputedFeeToken = accountAssets.data.find(
      (token) => token.name === feeToken.asset?.name,
    )
    if (!preComputedFeeToken || !preComputedFeeToken?.balanceOf?.value) {
      setFundsDeposited(false)
      return
    }

    const feeTokenBalance = preComputedFeeToken.balanceOf.value
    const requiredAmount = parseUnits(tokenAmount, feeToken.asset?.decimals)

    setFundsDeposited(feeTokenBalance >= requiredAmount)
  }, [feeToken, accountAssets, setFundsDeposited, tokenAmount])

  // get from localstorage and set AI choices if amount already deposit

  useEffect(() => {
    if (!isTokenAlreadyDeposited || !eoaAddress) return
    const previousAiChatChoices = localStorageService.getAiChatChoices(
      eoaAddress,
      token,
    )
    if (!previousAiChatChoices) return
    const { preferredVaults, rebalanceFrequency } = previousAiChatChoices

    setPreferredVaults(preferredVaults)
    setSelectedFrequency(rebalanceFrequency)
  }, [eoaAddress, isTokenAlreadyDeposited, token])

  useEffect(() => {
    if (!paramsDetectedInAiRes) return
    const amountToSet = (paramsDetectedInAiRes.amount || "0").toString()

    const allowedEthAddresses = paramsDetectedInAiRes.preferredVaults.filter(
      (address) => isValidEthereumAddress(address),
    )

    const rebalanceFrequency = (
      paramsDetectedInAiRes.every || frequencyOptions[1].value
    ).toString()

    setPreferredVaults(allowedEthAddresses)
    setSelectedFrequency(rebalanceFrequency)
    // set in localstorage

    eoaAddress &&
      localStorageService.setAiChatChoices(eoaAddress, token, {
        rebalanceFrequency,
        preferredVaults: allowedEthAddresses,
      })

    const formattedMax = formatUnits(
      getMaxTokenBalanceAvailable,
      tokenConfig.decimals,
    )
    const isAmountGreaterThanMax =
      BigInt(parseUnits(amountToSet, tokenConfig?.decimals)) >
      getMaxTokenBalanceAvailable

    isAmountGreaterThanMax &&
      dispatchToast({
        id: "error",
        type: "error",
        title: "Amount exceeds wallet balance",
        description: { value: "Amount set to max balance" },
      })
    console.log({ formattedMax, isAmountGreaterThanMax, amountToSet })

    isAmountGreaterThanMax
      ? setTokenAmount(formattedMax)
      : setTokenAmount(amountToSet)
    setParamsDetectedInAiRes(null)
    setIsAiPromptValuesSet(true)
  }, [
    eoaAddress,
    getMaxTokenBalanceAvailable,
    openDepositModal,
    paramsDetectedInAiRes,
    setParamsDetectedInAiRes,
    token,
    tokenConfig?.decimals,
  ])
  return {
    eoaAddress,
    chainId,
    activeTab,
    setActiveTab,
    tokenAmount,
    setTokenAmount,
    selectedFrequency,
    setSelectedFrequency,
    isDepositModalOpen,
    closeDepositModal,
    openDepositModal,
    tokenConfig,
    getMaxTokenBalanceAvailable,
    handleDepositFunds,
    feeEstimate,
    accountAssets,
    alreadyDepositedValue,
    handleDeployConsole,
    isFeeGreaterThanDeposit,
    preComputedConsoleAddress,
    fundsDepositedLoading,
    consoleDeployedLoading,
    waitForDepositLoading,
    withdrawAmount,
    isWithdrawModalOpen: withdrawModalData.isModalOpen,
    closeWithdrawModal,
    openWithdrawModal,
    withdrawModalState: withdrawModalData.withdrawModalState,
    withdrawModalSuccessData: withdrawModalData.sucessState,
    preferredVaults,
    isAiPromptValueSet,
    isTokenAlreadyDeposited,
  }
}
