import { Address, GetLogsReturnType } from 'viem'
import { SplitV2Type } from '../types'
import { splitMainPolygonAbi } from './abi/splitMain'
import { splitV2ABI } from './abi/splitV2'
import { splitV2FactoryABI } from './abi/splitV2Factory'
import { InvalidArgumentError } from '../errors'

export const PERCENTAGE_SCALE = BigInt(1e6)

// https://github.com/mds1/multicall
export const MULTICALL_3_ADDRESS = '0xca11bde05977b3631167028862be2a173976ca11'

export const REVERSE_RECORDS_ADDRESS =
  '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C'

const SPLIT_MAIN_ADDRESS = '0x2ed6c4B5dA6378c7897AC67Ba9e43102Feb694EE'
const VESTING_MODULE_FACTORY_ADDRESS =
  '0x0a2841630f198745a55c4dab3fe98f77271949e5'
const WATERFALL_MODULE_FACTORY_ADDRESS =
  '0x4Df01754eBd055498C8087b1e9a5c7a9ad19b0F6'
const LIQUID_SPLIT_FACTORY_ADDRESS =
  '0xdEcd8B99b7F763e16141450DAa5EA414B7994831'
const RECOUP_ADDRESS = '0xCbB386B801Ec72A5aB02AEB723dECd12f96EdE41'
const PASS_THROUGH_WALLET_FACTORY_ADDRESS_MAINNET =
  '0xF5aCC1568706Fbf9A55a77DdBe8DF907Da95dD6B'
const PASS_THROUGH_WALLET_FACTORY_ADDRESS =
  '0x52d6838957ec268cc5B50F17F9b490cbAb0A9E40'
const SWAPPER_FACTORY_ADDRESS = '0xa244bbe019cf1BA177EE5A532250be2663Fb55cA'
const UNI_V3_SWAP_ADDRESS = '0x981a6aC55c7D39f50666938CcD0df53D59797e87'
// Keep in sync with subgraph
const DIVERSIFIER_FACTORY_ADDRESS_MAINNET =
  '0x78791997483f25217F4C3FE2a568Fe3eFaf77884'
const DIVERSIFIER_FACTORY_ADDRESS = '0x1f3f5C7342Ae19E2b35b657864106f227201eF8A'

const SPLIT_MAIN_ADDRESS_BSC = '0x5924cD81dC672151527B1E4b5Ef57B69cBD07Eda'
const VESTING_MODULE_FACTORY_ADDRESS_BSC =
  '0x7205d93721837c45Be23C930D9fba842e968Ad69'
const WATERFALL_MODULE_FACTORY_ADDRESS_BSC =
  '0xB7CCCcCeb459F0910589556123dC5fA6DC8dE4E0'
const LIQUID_SPLIT_FACTORY_ADDRESS_BSC =
  '0xCDe071bE119024EdC970B3Da15003ee834ae40D2'
const RECOUP_ADDRESS_BSC = '0x5ff0C88311F79803B43e9Dc3F2B20F49A6b680fd'

const SPLIT_MAIN_ADDRESS_HOLESKY = '0xfC8a305728051367797DADE6Aa0344E0987f5286'
const LIQUID_SPLIT_FACTORY_ADDRESS_HOLESKY =
  '0xAbA0E852f1EB10196b55f877903A87a2588b7aa8'
const RECOUP_ADDRESS_HOLESKY = '0xcFba37C5Ee4d80c286593342470EB881deb9799e'

const SPLIT_MAIN_ADDRESS_SEPOLIA = '0x54E4a6014D36c381fC43b7E24A1492F556139a6F'
const LIQUID_SPLIT_FACTORY_ADDRESS_SEPOLIA =
  '0xb3Af150A5902e06373A2D3f177d85435A48c6b33'
const RECOUP_ADDRESS_SEPOLIA = '0x8Cbb4e187ce8A29BACC13Fd999a107f3c4b46D3B'
const DIVERSIFIER_FACTORY_ADDRESS_SEPOLIA =
  '0x0eAeAfD1c82563B6005c7D09031462D9FF68Adab'

const WAREHOUSE_ADDRESS = '0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8'
export const PULL_SPLIT_FACTORY_ADDRESS =
  '0x80f1B766817D04870f115fEBbcCADF8DBF75E017'
export const PUSH_SPLIT_FACTORY_ADDRESS =
  '0xaDC87646f736d6A82e9a6539cddC488b2aA07f38'

export const PULL_SPLIT_V2o1_FACTORY_ADDRESS =
  '0x5cbA88D55Cec83caD5A105Ad40C8c9aF20bE21d1'
export const PUSH_SPLIT_V2o1_FACTORY_ADDRESS =
  '0xDc6259E13ec0621e6F19026b2e49D846525548Ed'

export const PULL_SPLIT_V2o1_ADDRESS =
  '0xF9C25250523Df26343222fC46de932355B850c97'
export const PUSH_SPLIT_V2o1_ADDRESS =
  '0x3f81D81e0884abD8Cc4583a704a9397972623214'

export const getSplitMainAddress = (chainId: number): Address => {
  if (chainId === ChainId.BSC) return SPLIT_MAIN_ADDRESS_BSC
  if (chainId === ChainId.HOLESKY) return SPLIT_MAIN_ADDRESS_HOLESKY
  if (chainId === ChainId.SEPOLIA) return SPLIT_MAIN_ADDRESS_SEPOLIA
  return SPLIT_MAIN_ADDRESS
}

export const getVestingFactoryAddress = (chainId: number): Address => {
  if (chainId === ChainId.BSC) return VESTING_MODULE_FACTORY_ADDRESS_BSC
  return VESTING_MODULE_FACTORY_ADDRESS
}

export const getWaterfallFactoryAddress = (chainId: number): Address => {
  if (chainId === ChainId.BSC) return WATERFALL_MODULE_FACTORY_ADDRESS_BSC
  return WATERFALL_MODULE_FACTORY_ADDRESS
}

export const getLiquidSplitFactoryAddress = (chainId: number): Address => {
  if (chainId === ChainId.BSC) return LIQUID_SPLIT_FACTORY_ADDRESS_BSC
  if (chainId === ChainId.HOLESKY) return LIQUID_SPLIT_FACTORY_ADDRESS_HOLESKY
  if (chainId === ChainId.SEPOLIA) return LIQUID_SPLIT_FACTORY_ADDRESS_SEPOLIA
  return LIQUID_SPLIT_FACTORY_ADDRESS
}

export const getRecoupAddress = (chainId: number): Address => {
  if (chainId === ChainId.BSC) return RECOUP_ADDRESS_BSC
  if (chainId === ChainId.HOLESKY) return RECOUP_ADDRESS_HOLESKY
  if (chainId === ChainId.SEPOLIA) return RECOUP_ADDRESS_SEPOLIA
  return RECOUP_ADDRESS
}

export const getPassThroughWalletFactoryAddress = (
  chainId: number,
): Address => {
  if (chainId == ChainId.MAINNET)
    return PASS_THROUGH_WALLET_FACTORY_ADDRESS_MAINNET
  return PASS_THROUGH_WALLET_FACTORY_ADDRESS
}

export const getSwapperFactoryAddress = (): Address => {
  return SWAPPER_FACTORY_ADDRESS
}

/**
 * @param chainId - The chain ID to get the UniV3Swap address for.
 * @returns The UniV3Swap address for the given chain ID.
 * @dev It is recommended to pass in the chainId.
 */
export const getUniV3SwapAddress = (chainId?: number): Address => {
  if (!chainId) return UNI_V3_SWAP_ADDRESS

  if (!PERMISSIONLESS_SWAPPER_CHAIN_IDS.includes(chainId))
    throw new InvalidArgumentError(
      `Chain - ${chainId} is not supported for permissionless swaps`,
    )
  if (chainId === ChainId.POLYGON)
    return '0x8d582AEDf0326348960054021ab0b748B3A2BA66'
  if (chainId === ChainId.OPTIMISM)
    return '0x8d582AEDf0326348960054021ab0b748B3A2BA66'
  return UNI_V3_SWAP_ADDRESS
}

export const getDiversifierFactoryAddress = (chainId: number): Address => {
  if (chainId === ChainId.MAINNET) return DIVERSIFIER_FACTORY_ADDRESS_MAINNET
  if (chainId === ChainId.SEPOLIA) return DIVERSIFIER_FACTORY_ADDRESS_SEPOLIA
  return DIVERSIFIER_FACTORY_ADDRESS
}

export const getWarehouseAddress = (): Address => {
  return WAREHOUSE_ADDRESS
}

export const getSplitV2FactoryAddress = (
  _chainId: number,
  type: SplitV2Type,
): Address => {
  if (type === SplitV2Type.Pull) return PULL_SPLIT_FACTORY_ADDRESS
  else return PUSH_SPLIT_FACTORY_ADDRESS
}

export const getSplitV2o1FactoryAddress = (
  _chainId: number,
  type: SplitV2Type,
): Address => {
  if (type === SplitV2Type.Pull) return PULL_SPLIT_V2o1_FACTORY_ADDRESS
  else return PUSH_SPLIT_V2o1_FACTORY_ADDRESS
}

export const getSplitV1StartBlock = (chainId: number): bigint => {
  if (!CHAIN_INFO[chainId].startBlock) throw new Error('Chain not supported')
  return BigInt(CHAIN_INFO[chainId].startBlock as number)
}

export const getSplitV2FactoriesStartBlock = (chainId: number): bigint => {
  if (!CHAIN_INFO[chainId].startBlockV2) throw new Error('Chain not supported')
  return BigInt(CHAIN_INFO[chainId].startBlockV2 as number)
}

export enum ChainId {
  MAINNET = 1,
  SEPOLIA = 11155111,
  HOLESKY = 17000,
  HOODI = 560048,
  POLYGON = 137,
  OPTIMISM = 10,
  OPTIMISM_SEPOLIA = 11155420,
  ARBITRUM = 42161,
  GNOSIS = 100,
  BSC = 56,
  ZORA = 7777777,
  ZORA_SEPOLIA = 999999999,
  BASE = 8453,
  BASE_SEPOLIA = 84532,
  FOUNDRY = 31337,
  BLAST = 81457,
  SHAPE = 360,
  WORLDCHAIN = 480,
  PLUME = 98866,
  PLUME_TESTNET = 98867,
}

export const ETHEREUM_CHAIN_IDS = [ChainId.MAINNET]
export const ETHEREUM_TEST_CHAIN_IDS = [
  ChainId.SEPOLIA,
  ChainId.HOLESKY,
  ChainId.HOODI,
]
export const POLYGON_CHAIN_IDS = [ChainId.POLYGON]
export const OPTIMISM_CHAIN_IDS = [ChainId.OPTIMISM, ChainId.OPTIMISM_SEPOLIA]
export const ARBITRUM_CHAIN_IDS = [ChainId.ARBITRUM]
export const GNOSIS_CHAIN_IDS = [ChainId.GNOSIS]
export const BSC_CHAIN_IDS = [ChainId.BSC]
export const ZORA_CHAIN_IDS = [ChainId.ZORA, ChainId.ZORA_SEPOLIA]
export const BASE_CHAIN_IDS = [ChainId.BASE, ChainId.BASE_SEPOLIA]
export const BLAST_CHAIN_IDS = [ChainId.BLAST]
export const SHAPE_CHAIN_IDS = [ChainId.SHAPE]
export const WORLD_CHAIN_IDS = [ChainId.WORLDCHAIN]
export const PLUME_CHAIN_IDS = [ChainId.PLUME, ChainId.PLUME_TESTNET]

export const ALL_CHAIN_IDS = [
  ...ETHEREUM_CHAIN_IDS,
  ...ETHEREUM_TEST_CHAIN_IDS,
  ...POLYGON_CHAIN_IDS,
  ...OPTIMISM_CHAIN_IDS,
  ...ARBITRUM_CHAIN_IDS,
  ...GNOSIS_CHAIN_IDS,
  ...BSC_CHAIN_IDS,
  ...ZORA_CHAIN_IDS,
  ...BASE_CHAIN_IDS,
  ...BLAST_CHAIN_IDS,
  ...SHAPE_CHAIN_IDS,
  ...WORLD_CHAIN_IDS,
  ...PLUME_CHAIN_IDS,
]

export const SPLITS_SUPPORTED_CHAIN_IDS = [
  3,
  4,
  42,
  ...ETHEREUM_CHAIN_IDS,
  ChainId.SEPOLIA,
  ChainId.HOLESKY,
  ...POLYGON_CHAIN_IDS,
  ...OPTIMISM_CHAIN_IDS,
  ...ARBITRUM_CHAIN_IDS,
  ...GNOSIS_CHAIN_IDS,
  ...BSC_CHAIN_IDS,
  ...ZORA_CHAIN_IDS,
  ...BASE_CHAIN_IDS,
  ...BLAST_CHAIN_IDS,
]

export const SPLITS_V2_SUPPORTED_CHAIN_IDS = [
  ChainId.MAINNET,
  ChainId.OPTIMISM,
  ChainId.BASE,
  ChainId.ZORA,
  ChainId.POLYGON,
  ChainId.ARBITRUM,
  ChainId.SEPOLIA,
  ChainId.HOLESKY,
  ChainId.HOODI,
  ChainId.BASE_SEPOLIA,
  ChainId.ZORA_SEPOLIA,
  ChainId.OPTIMISM_SEPOLIA,
  ChainId.FOUNDRY,
  ChainId.GNOSIS,
  ChainId.BSC,
  ChainId.SHAPE,
  ChainId.WORLDCHAIN,
  ChainId.PLUME,
  ChainId.PLUME_TESTNET,
]

// These chains use a different value for block.number than the actual block number
// for the transaction. This breaks the regular flow in the SDK for the splitV2o1
// contract. Need to fall back to the regular logs flow for these chains.
export const INVALID_BLOCK_NUMBER_CHAIN_IDS = [
  ChainId.ARBITRUM,
  ChainId.PLUME,
  ChainId.PLUME_TESTNET,
]
export const SPLITS_SUBGRAPH_CHAIN_IDS = [
  ChainId.MAINNET,
  ChainId.OPTIMISM,
  ChainId.BASE,
  ChainId.ZORA,
  ChainId.POLYGON,
  ChainId.ARBITRUM,
  ChainId.GNOSIS,
  ChainId.BSC,
  ChainId.BLAST,
  ChainId.HOLESKY,
  ChainId.SEPOLIA,
  ChainId.BASE_SEPOLIA,
]
export const WATERFALL_CHAIN_IDS = ALL_CHAIN_IDS.slice().filter(
  (id) =>
    id !== ChainId.ZORA_SEPOLIA &&
    id !== ChainId.BASE_SEPOLIA &&
    id !== ChainId.BLAST,
)
export const LIQUID_SPLIT_CHAIN_IDS = ALL_CHAIN_IDS.slice().filter(
  (id) =>
    id !== ChainId.ZORA_SEPOLIA &&
    id !== ChainId.BASE_SEPOLIA &&
    id !== ChainId.BLAST,
)
export const VESTING_CHAIN_IDS = ALL_CHAIN_IDS.slice().filter(
  (id) =>
    id !== ChainId.ZORA_SEPOLIA &&
    id !== ChainId.BASE_SEPOLIA &&
    id !== ChainId.BLAST,
)
export const TEMPLATES_CHAIN_IDS = ALL_CHAIN_IDS.slice().filter(
  (id) =>
    id !== ChainId.ZORA_SEPOLIA &&
    id !== ChainId.BASE_SEPOLIA &&
    id !== ChainId.BLAST,
)

export const SWAPPER_CHAIN_IDS = [
  ChainId.MAINNET,
  ChainId.SEPOLIA,
  ChainId.BASE,
  ChainId.POLYGON,
  ChainId.OPTIMISM,
  ChainId.ARBITRUM,
]

export const PERMISSIONLESS_SWAPPER_CHAIN_IDS = [
  ChainId.MAINNET,
  ChainId.POLYGON,
  ChainId.OPTIMISM,
  ChainId.ARBITRUM,
]

export const PASS_THROUGH_WALLET_CHAIN_IDS = SWAPPER_CHAIN_IDS.slice()
export const ORACLE_CHAIN_IDS = SWAPPER_CHAIN_IDS.slice()
export const DIVERSIFIER_CHAIN_IDS = SWAPPER_CHAIN_IDS.slice()

export const SPLITS_MAX_PRECISION_DECIMALS = 4
export const LIQUID_SPLITS_MAX_PRECISION_DECIMALS = 1

export const LIQUID_SPLIT_NFT_COUNT = 1000
export const LIQUID_SPLIT_URI_BASE_64_HEADER = 'data:application/json;base64,'

export const CHAIN_INFO: {
  [chainId: number]: {
    startBlock: number
    wrappedNativeTokenAddress?: Address
    nativeCurrency: { symbol: string }
    startBlockV2?: number
  }
} = {
  [ChainId.MAINNET]: {
    startBlock: 14206768,
    nativeCurrency: {
      symbol: 'ETH',
    },
    wrappedNativeTokenAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
    startBlockV2: 19451952,
  },
  [ChainId.SEPOLIA]: {
    startBlock: 4836125,
    nativeCurrency: {
      symbol: 'ETH',
    },
    wrappedNativeTokenAddress: '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14',
    startBlockV2: 5467056,
  },
  [ChainId.HOLESKY]: {
    startBlock: 148241,
    nativeCurrency: {
      symbol: 'ETH',
    },
    startBlockV2: 1121603,
  },
  [ChainId.HOODI]: {
    startBlock: 106772,
    nativeCurrency: {
      symbol: 'ETH',
    },
    startBlockV2: 106772,
  },
  [ChainId.POLYGON]: {
    startBlock: 25303316,
    nativeCurrency: {
      symbol: 'MATIC',
    },
    wrappedNativeTokenAddress: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
    startBlockV2: 54572664,
  },
  [ChainId.OPTIMISM]: {
    startBlock: 24704537,
    nativeCurrency: {
      symbol: 'ETH',
    },
    wrappedNativeTokenAddress: '0x4200000000000000000000000000000000000006',
    startBlockV2: 117327692,
  },
  [ChainId.ARBITRUM]: {
    startBlock: 26082503,
    nativeCurrency: {
      symbol: 'ETH',
    },
    wrappedNativeTokenAddress: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
    startBlockV2: 189649987,
  },
  [ChainId.GNOSIS]: {
    startBlock: 26014830,
    nativeCurrency: {
      symbol: 'xDai',
    },
    startBlockV2: 33277147,
  },
  [ChainId.BSC]: {
    startBlock: 24962607,
    nativeCurrency: {
      symbol: 'BNB',
    },
    startBlockV2: 37570236,
  },
  [ChainId.ZORA]: {
    startBlock: 1860322,
    nativeCurrency: {
      symbol: 'ETH',
    },
    startBlockV2: 11780035,
  },
  [ChainId.ZORA_SEPOLIA]: {
    startBlock: 2296044,
    nativeCurrency: {
      symbol: 'ETH',
    },
    startBlockV2: 6062586,
  },
  [ChainId.BASE]: {
    startBlock: 2293907,
    nativeCurrency: {
      symbol: 'ETH',
    },
    wrappedNativeTokenAddress: '0x4200000000000000000000000000000000000006',
    startBlockV2: 11732477,
  },
  [ChainId.BASE_SEPOLIA]: {
    startBlock: 3324413,
    nativeCurrency: {
      symbol: 'ETH',
    },
    startBlockV2: 7243250,
  },
  [ChainId.BLAST]: {
    startBlock: 220516,
    nativeCurrency: {
      symbol: 'ETH',
    },
  },
  [ChainId.SHAPE]: {
    startBlock: 5448039,
    nativeCurrency: {
      symbol: 'ETH',
    },
    startBlockV2: 5448039,
  },
  [ChainId.WORLDCHAIN]: {
    startBlock: 9116639,
    nativeCurrency: {
      symbol: 'ETH',
    },
    startBlockV2: 9116639,
  },
  [ChainId.PLUME]: {
    startBlock: 865172,
    nativeCurrency: {
      symbol: 'PLUME',
    },
    startBlockV2: 865172,
  },
  [ChainId.PLUME_TESTNET]: {
    startBlock: 1759,
    nativeCurrency: {
      symbol: 'PLUME',
    },
    startBlockV2: 1759,
  },
}

export enum TransactionType {
  Transaction = 'Transaction',
  CallData = 'CallData',
  GasEstimate = 'GasEstimate',
  Signature = 'Signature',
}

export const ZERO = BigInt(0)
export const ONE = BigInt(1)
export const TWO = BigInt(2)

export const NATIVE_TOKEN_ADDRESS: Address =
  '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'

export const splitV1UpdatedEvent = splitMainPolygonAbi[18]
export const splitV1CreatedEvent = splitMainPolygonAbi[14]
type SplitV1UpdatedEventType = typeof splitV1UpdatedEvent
export type SplitV1UpdatedLogType = GetLogsReturnType<
  SplitV1UpdatedEventType,
  [SplitV1UpdatedEventType],
  true,
  bigint,
  bigint
>[0]
type SplitV1CreatedEventType = typeof splitV1CreatedEvent
export type SplitV1CreatedLogType = GetLogsReturnType<
  SplitV1CreatedEventType,
  [SplitV1CreatedEventType],
  true,
  bigint,
  bigint
>[0]

export const splitV2UpdatedEvent = splitV2ABI[28]
export const splitV2CreatedEvent = splitV2FactoryABI[8]
type SplitV2UpdatedEventType = typeof splitV2UpdatedEvent
export type SplitV2UpdatedLogType = GetLogsReturnType<
  SplitV2UpdatedEventType,
  [SplitV2UpdatedEventType],
  true,
  bigint,
  bigint
>[0]
type SplitV2CreatedEventType = typeof splitV2CreatedEvent
export type SplitV2CreatedLogType = GetLogsReturnType<
  SplitV2CreatedEventType,
  [SplitV2CreatedEventType],
  true,
  bigint,
  bigint
>[0]
