import { Address, ConsoleKit } from "brahma-console-kit"
import { createPublicClient, fromHex, Hex, http } from "viem"
import { base } from "viem/chains"

import { PROD_URL } from "@/components/morphoStrategy/constants"
import { URL_API_KEY_PROD } from "@/constants"
import { TAsset } from "@/types"
import { convertToFullString, formatUnits, isHexMatch } from "@/utils"
import { Trade } from "./types"
import { ERC20_TRANSFER_TOPIC, GNOSIS_SAFE_ABI } from "./constants"

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

const API_URL = PROD_URL
const API_KEY = URL_API_KEY_PROD

// const API_URL = DEV_URL
// const API_KEY = URL_API_KEY_DEV
const kit = new ConsoleKit(API_KEY, API_URL)

/**
 * Index positions for topics in Ethereum event logs.
 *
 * Ethereum logs store event data in an indexed `topics` array, where:
 * - `topicIndex` (0) holds the event signature (e.g., Transfer event hash)
 * - `fromIndex` (1) holds the sender's address (for Transfer events, it's the sender of the tokens)
 * - `toIndex` (2) holds the recipient's address (for Transfer events, it's the receiver of the tokens)
 */
const topicIndex = 0
const fromIndex = 1
const toIndex = 2

/**
 * Fetches and processes trades based on agent logs.
 *
 * This function retrieves agent logs, extracts relevant transaction details,
 * fetches transaction receipts and block data, and determines token transfer details.
 *
 * @param {string} agentId - The ID of the agentId.
 * @param {TAsset} targetToken - The target token being swapped to.
 * @param {TAsset} allocatedToken - The token being swapped from.
 * @param {Address} eoa - The externally owned account (EOA) executing the trades.
 * @param {Address} subAccountAddress - The sub-account address handling the transactions.
 * @returns {Promise<Trade[]>} A promise resolving to an array of trade objects.
 */
export const fetchTrades = async (
  agentId: string,
  targetToken: TAsset,
  allocatedToken: TAsset,
  eoa: Address,
  subAccountAddress: Address,
): Promise<Trade[]> => {
  console.log("Fetching agent logs for agentId:", agentId)

  const txns = (await kit.automationContext.fetchAutomationLogs(agentId)) || []
  if (!txns.length) {
    console.warn("No transactions found for agentId:", agentId)
    return []
  }

  console.log("Retrieved", txns.length, "transactions")

  // Extract transaction hashes
  const txnHashes = txns.map((log) => log.outputTxHash as Hex)

  console.log(
    "Fetching transaction receipts for",
    txnHashes.length,
    "transactions",
  )

  // Fetch all transaction receipts in parallel
  const receipts = await Promise.all(
    txnHashes.map((hash) => basePublicClient.getTransactionReceipt({ hash })),
  )

  // Extract block numbers from receipts
  const blockNumbers = receipts.map((receipt) => receipt.blockNumber)

  console.log("Fetching block data for", blockNumbers.length, "blocks")

  // Fetch all block timestamps in parallel
  const blocks = await Promise.all(
    blockNumbers.map((blockNumber) =>
      basePublicClient.getBlock({ blockNumber }),
    ),
  )

  return txns
    .map((txn, index) => {
      const receipt = receipts[index]
      const block = blocks[index]

      if (!receipt || !block) return null

      const logs = receipt.logs
      const timestamp = block.timestamp
        ? new Date(Number(block.timestamp) * 1000).toLocaleString()
        : ""

      if (logs.length < 3) return null

      console.log("Processing logs for transaction:", txn.outputTxHash)

      const tokenInlog = logs.find((log, index) => {
        try {
          return (
            index > 0 && // not first element
            isHexMatch(log.topics[topicIndex], ERC20_TRANSFER_TOPIC) &&
            isHexMatch(log.address, allocatedToken.address) &&
            isHexMatch(log.topics[fromIndex], subAccountAddress)
          )
        } catch (error) {
          console.error("Error processing tokenInlog:", error)
          return false
        }
      })

      const tokenOutlog = logs.find((log) => {
        try {
          return (
            isHexMatch(log.topics[topicIndex], ERC20_TRANSFER_TOPIC) &&
            isHexMatch(log.address, targetToken.address) &&
            isHexMatch(log.topics[toIndex], subAccountAddress)
          )
        } catch (error) {
          console.error("Error processing tokenOutlog:", error)
          return false
        }
      })

      const sentToWalletLog = logs.find((log) => {
        try {
          return (
            isHexMatch(log.topics[topicIndex], ERC20_TRANSFER_TOPIC) &&
            isHexMatch(log.address, targetToken.address) &&
            isHexMatch(log.topics[fromIndex], subAccountAddress) &&
            isHexMatch(log.topics[toIndex], eoa)
          )
        } catch (error) {
          console.error("Error processing sentToWalletLog:", error)
          return false
        }
      })

      if (!tokenInlog || !tokenOutlog || !sentToWalletLog) {
        console.warn("Missing logs for transaction:", txn.outputTxHash)
        return null
      }

      console.log("Logs matched for transaction:", txn.outputTxHash)

      return {
        txnHash: txn.outputTxHash as Hex,
        date: timestamp,
        tokenInData: {
          asset: allocatedToken,
          amount: formatUnits(
            convertToFullString(fromHex(tokenInlog.data ?? "0x00", "number")),
            allocatedToken.decimals,
          ),
        },
        tokenOutData: {
          asset: targetToken,
          amount: formatUnits(
            convertToFullString(fromHex(tokenOutlog.data ?? "0x00", "number")),
            targetToken.decimals,
          ),
        },
        sentToWalletAmount: formatUnits(
          convertToFullString(
            fromHex(
              !sentToWalletLog.data || sentToWalletLog?.data === "0x"
                ? "0x00"
                : sentToWalletLog?.data,
              "number",
            ),
          ),
          targetToken.decimals,
        ),
      }
    })
    .filter((trade) => trade !== null)
}

export const isOneToOneSafe = async (
  address: Address,
): Promise<boolean | undefined> => {
  try {
    if (!basePublicClient) return undefined

    const [owners, threshold] = await Promise.all([
      basePublicClient.readContract({
        address: address,
        abi: GNOSIS_SAFE_ABI,
        functionName: "getOwners",
      }),
      basePublicClient.readContract({
        address: address,
        abi: GNOSIS_SAFE_ABI,
        functionName: "getThreshold",
      }),
    ])

    return owners.length === 1 && threshold === BigInt(1)
  } catch (error) {
    console.error(`Error checking ${address}:`, error)
    return false // Or handle the error differently, e.g., re-throw
  }
}

export const lowercaseKeys = (
  obj: Record<string, string>,
): Record<string, string> =>
  Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [key.toLowerCase(), value]),
  )

export const calculateAutomationEvery = (
  amount: string,
  price: number | null,
  duration: number, //hours
  maxTradeSize: number,
) => {
  const parsedAmount = parseFloat(amount)
  const twoPercentOfAmount = parsedAmount * 0.02
  const twoDollarEquivalentAmount = price ? 2 / price : 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),
  )

  return formattedDuration
}
export const formatDurationToHumanReadable = (seconds: number): string => {
  const units = [
    { value: 86400, singular: "day", plural: "days" },
    { value: 3600, singular: "hour", plural: "hours" },
    { value: 60, singular: "minute", plural: "minutes" },
    { value: 1, singular: "second", plural: "seconds" },
  ]

  // Handle zero case
  if (seconds === 0) return "0 seconds"

  // Find the two largest units
  let result = ""
  let count = 0

  for (const unit of units) {
    if (seconds >= unit.value && count < 2) {
      const value = Math.floor(seconds / unit.value)
      seconds %= unit.value

      if (result) result += " "
      result += `${value} ${value === 1 ? unit.singular : unit.plural}`
      count++
    }
  }

  return result
}
