"use client";

import { useState, useEffect, useCallback } from "react";
import { Container, useToast, Center, Spacer, Flex, VStack } from "@chakra-ui/react";

import { SingleValue } from "chakra-react-select";
// WC v2
import { Core } from "@walletconnect/core";
import { WalletKit, IWalletKit } from "@reown/walletkit";
import { ProposalTypes, SessionTypes } from "@walletconnect/types";
import { getSdkError, parseUri } from "@walletconnect/utils";
import { ethers } from "ethers";
import axios from "axios";
import networksList from "evm-rpcs-list";
import { useSafeInject } from "../../contexts/SafeInjectContext";
import { useTransaction } from "../../contexts/TransactionContext";
import { useSmartWallet } from "../../contexts/SmartWalletContext";
import { TransactionExecutionMethod } from "../../types";
import TenderlySettings from "./TenderlySettings";
import AddressInput from "./AddressInput";
import { SelectedNetworkOption, TxnDataType } from "../../types";
import NetworkInput from "./NetworkInput";
import TabsSelect from "./TabsSelect";
import WalletConnectTab from "./WalletConnectTab";
import IFrameConnectTab from "./IFrameConnectTab";
import BrowserExtensionTab from "./BrowserExtensionTab";
import TransactionRequests from "./TransactionRequests";
import NotificationBar from "./NotificationBar";
import WalletManager from "../SmartWallet/WalletManager";
import OwnerManagement from "../SmartWallet/OwnerManagement";
import WalletBalance from "../Balance/WalletBalance";
import TransactionApproval from "../TransactionExecution/TransactionApproval";
import TransactionBuilder from "../TransactionExecution/TransactionBuilder";
import TransactionHistory from "../TransactionExecution/TransactionHistory";

const WCMetadata = {
  name: "Impersonator",
  description: "Login to dapps as any address",
  url: "www.impersonator.xyz",
  icons: ["https://www.impersonator.xyz/favicon.ico"],
};

const core = new Core({
  projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID || "demo-project-id",
});

const primaryNetworkIds = [
  1, // ETH Mainnet
  42161, // Arbitrum One
  43114, // Avalanche
  80094, // Berachain
  56, // BSC
  8453, // Base
  250, // Fantom Opera
  100, // Gnosis
  10, // Optimism
  137, // Polygon
  130, // Unichain
];

const primaryNetworkOptions = primaryNetworkIds.map((id) => {
  return { chainId: id, ...networksList[id.toString()] };
});
const secondaryNetworkOptions = Object.entries(networksList)
  .filter((id) => !primaryNetworkIds.includes(parseInt(id[0])))
  .map((arr) => {
    return {
      chainId: parseInt(arr[0]),
      name: arr[1].name,
      rpcs: arr[1].rpcs,
    };
  });
const allNetworksOptions = [
  ...primaryNetworkOptions,
  ...secondaryNetworkOptions,
];

function Body() {
  let addressFromURL: string | null = null;
  let showAddressCache: string | null = null;
  let urlFromURL: string | null = null;
  let urlFromCache: string | null = null;
  let chainFromURL: string | null = null;
  let tenderlyForkIdCache: string | null = null;

  if (typeof window !== "undefined") {
    const urlParams = new URLSearchParams(window.location.search);
    addressFromURL = urlParams.get("address");
    urlFromURL = urlParams.get("url");
    chainFromURL = urlParams.get("chain");
  }
  // Use sessionStorage for UI preferences (non-sensitive)
  if (typeof sessionStorage !== "undefined") {
    showAddressCache = sessionStorage.getItem("showAddress") ?? null;
    urlFromCache = sessionStorage.getItem("appUrl") ?? null;
    tenderlyForkIdCache = sessionStorage.getItem("tenderlyForkId") ?? null;
  }
  let networkIdViaURL = 1;
  if (chainFromURL) {
    for (let i = 0; i < allNetworksOptions.length; i++) {
      if (
        allNetworksOptions[i].name
          .toLowerCase()
          .includes(chainFromURL.toLowerCase())
      ) {
        networkIdViaURL = allNetworksOptions[i].chainId;
        break;
      }
    }
  }
  const toast = useToast();

  const {
    setAddress: setIFrameAddress,
    appUrl,
    setAppUrl,
    setRpcUrl,
    iframeRef,
    latestTransaction,
  } = useSafeInject();
  const { createTransaction, defaultExecutionMethod } = useTransaction();
  const { activeWallet } = useSmartWallet();

  const [provider, setProvider] = useState<ethers.providers.JsonRpcProvider>();
  const [showAddress, setShowAddress] = useState(
    addressFromURL ?? showAddressCache ?? ""
  ); // gets displayed in input. ENS name remains as it is
  const [address, setAddress] = useState(
    addressFromURL ?? showAddressCache ?? ""
  ); // internal resolved address
  const [isAddressValid, setIsAddressValid] = useState(true);
  const [uri, setUri] = useState("");
  const [networkId, setNetworkId] = useState(networkIdViaURL);
  const [selectedNetworkOption, setSelectedNetworkOption] = useState<
    SingleValue<SelectedNetworkOption>
  >({
    label: networksList[networkIdViaURL].name,
    value: networkIdViaURL,
  });
  // WC v2
  const [web3wallet, setWeb3Wallet] = useState<IWalletKit>();
  const [web3WalletSession, setWeb3WalletSession] =
    useState<SessionTypes.Struct>();
  const [isConnected, setIsConnected] = useState(false);
  const [loading, setLoading] = useState(false);

  const [selectedTabIndex, setSelectedTabIndex] = useState(urlFromURL ? 1 : 0);
  const [isIFrameLoading, setIsIFrameLoading] = useState(false);

  const [inputAppUrl, setInputAppUrl] = useState<string | undefined>(
    urlFromURL ?? urlFromCache ?? undefined
  );
  const [iframeKey, setIframeKey] = useState(0); // hacky way to reload iframe when key changes

  const [tenderlyForkId, setTenderlyForkId] = useState(
    tenderlyForkIdCache ?? ""
  );
  const [sendTxnData, setSendTxnData] = useState<TxnDataType[]>([]);

  useEffect(() => {
    // only use cached address if no address from url provided
    if (!addressFromURL) {
      // getCachedSession - use sessionStorage for UI preferences
      const _showAddress = typeof sessionStorage !== "undefined" 
        ? sessionStorage.getItem("showAddress") ?? undefined
        : undefined;
      // WC V2
      initWeb3Wallet(true, _showAddress);
    }

    setProvider(
      new ethers.providers.JsonRpcProvider(
        `https://mainnet.infura.io/v3/${process.env.NEXT_PUBLIC_INFURA_KEY}`
      )
    );
  }, []);

  useEffect(() => {
    updateNetwork((selectedNetworkOption as SelectedNetworkOption).value);
    // eslint-disable-next-line
  }, [selectedNetworkOption]);

  useEffect(() => {
    if (provider && addressFromURL && urlFromURL) {
      initIFrame();
    }
    // eslint-disable-next-line
  }, [provider]);

  useEffect(() => {
    // Use sessionStorage for UI preferences (non-sensitive)
    if (typeof sessionStorage !== "undefined") {
      sessionStorage.setItem("tenderlyForkId", tenderlyForkId);
    }
  }, [tenderlyForkId]);

  useEffect(() => {
    // Use sessionStorage for UI preferences (non-sensitive)
    if (typeof sessionStorage !== "undefined") {
      sessionStorage.setItem("showAddress", showAddress);
    }
  }, [showAddress]);

  useEffect(() => {
    // Use sessionStorage for UI preferences (non-sensitive)
    if (inputAppUrl && typeof sessionStorage !== "undefined") {
      sessionStorage.setItem("appUrl", inputAppUrl);
    }
  }, [inputAppUrl]);

  useEffect(() => {
    setIFrameAddress(address);
    // eslint-disable-next-line
  }, [address]);

  useEffect(() => {
    // TODO: use random rpc if this one is slow/down?
    setRpcUrl(networksList[networkId].rpcs[0]);
    // eslint-disable-next-line
  }, [networkId]);

  useEffect(() => {
    if (latestTransaction) {
      const newTxn = {
        from: address,
        ...latestTransaction,
      };

      setSendTxnData((data) => {
        if (data.some((d) => d.id === newTxn.id)) {
          return data;
        } else {
          return [
            { ...newTxn, value: ethers.BigNumber.from("0x" + newTxn.value).toString() },
            ...data,
          ];
        }
      });

      if (tenderlyForkId.length > 0) {
        axios
          .post("https://rpc.tenderly.co/fork/" + tenderlyForkId, {
            jsonrpc: "2.0",
            id: newTxn.id,
            method: "eth_sendTransaction",
            params: [
              {
                from: newTxn.from,
                to: newTxn.to,
                value: newTxn.value,
                data: newTxn.data,
              },
            ],
          })
          .then((res) => {
            console.log(res.data);
            toast({
              title: "Txn Simulated on Tenderly",
              description: `Hash: ${res.data.result}`,
              status: "success",
              position: "bottom-right",
              duration: null,
              isClosable: true,
            });
          });
      }
    }
    // eslint-disable-next-line
  }, [latestTransaction, tenderlyForkId]);

  const initWeb3Wallet = async (
    onlyIfActiveSessions?: boolean,
    _showAddress?: string
  ) => {
    const _web3wallet = await WalletKit.init({
      core,
      metadata: WCMetadata,
    });

    if (onlyIfActiveSessions) {
      const sessions = _web3wallet.getActiveSessions();
      const sessionsArray = Object.values(sessions);
      console.log({ sessions });
      if (sessionsArray.length > 0) {
        const _address =
          sessionsArray[0].namespaces["eip155"].accounts[0].split(":")[2];
        console.log({ _showAddress, _address });
        setWeb3WalletSession(sessionsArray[0]);
        setShowAddress(
          _showAddress && _showAddress.length > 0 ? _showAddress : _address
        );
        if (!(_showAddress && _showAddress.length > 0) && typeof sessionStorage !== "undefined") {
          sessionStorage.setItem("showAddress", _address);
        }
        setAddress(_address);
        setUri(
          `wc:${sessionsArray[0].pairingTopic}@2?relay-protocol=irn&symKey=xxxxxx`
        );
        setWeb3Wallet(_web3wallet);
        setIsConnected(true);
      }
    } else {
      setWeb3Wallet(_web3wallet);
      if (_showAddress) {
        setShowAddress(_showAddress);
        setAddress(_showAddress);
      }
    }

    // for debugging
    (window as any).w3 = _web3wallet;
  };

  const resolveAndValidateAddress = async () => {
    let isValid;
    let _address = address;
    if (!address) {
      isValid = false;
    } else {
      // Resolve ENS
      const resolvedAddress = await provider!.resolveName(address);
      if (resolvedAddress) {
        setAddress(resolvedAddress);
        _address = resolvedAddress;
        isValid = true;
      } else if (ethers.utils.isAddress(address)) {
        isValid = true;
      } else {
        isValid = false;
      }
    }

    setIsAddressValid(isValid);
    if (!isValid) {
      toast({
        title: "Invalid Address",
        description: "Address is not an ENS or Ethereum address",
        status: "error",
        isClosable: true,
        duration: 4000,
      });
    }

    return { isValid, _address: _address };
  };

  const initWalletConnect = async () => {
    setLoading(true);
    const { isValid } = await resolveAndValidateAddress();

    if (isValid) {
      const { version } = parseUri(uri);

      try {
        if (version === 1) {
          toast({
            title: "Couldn't Connect",
            description:
              "The dapp is still using the deprecated WalletConnect V1",
            status: "error",
            isClosable: true,
            duration: 8000,
          });
          setLoading(false);

          // let _legacySignClient = new LegacySignClient({ uri });

          // if (!_legacySignClient.connected) {
          //   await _legacySignClient.createSession();
          // }

          // setLegacySignClient(_legacySignClient);
          // setUri(_legacySignClient.uri);
        } else {
          await initWeb3Wallet();
        }
      } catch (err) {
        console.error(err);
        toast({
          title: "Couldn't Connect",
          description: "Refresh dApp and Connect again",
          status: "error",
          isClosable: true,
          duration: 2000,
        });
        setLoading(false);
      }
    } else {
      setLoading(false);
    }
  };

  const initIFrame = async (_inputAppUrl = inputAppUrl) => {
    setIsIFrameLoading(true);
    if (_inputAppUrl === appUrl) {
      setIsIFrameLoading(false);
      return;
    }

    const { isValid } = await resolveAndValidateAddress();
    if (!isValid) {
      setIsIFrameLoading(false);
      return;
    }

    setAppUrl(_inputAppUrl);
  };

  const onSessionProposal = useCallback(
    async (proposal: { params: { requiredNamespaces: Record<string, ProposalTypes.BaseRequiredNamespace>; optionalNamespaces?: Record<string, any> }; id: number }) => {
      if (loading) {
        setLoading(false);
      }
      console.log("EVENT", "session_proposal", proposal);

      const { requiredNamespaces, optionalNamespaces } = proposal.params;
      const namespaceKey = "eip155";
      const requiredNamespace = requiredNamespaces[namespaceKey] as
        | ProposalTypes.BaseRequiredNamespace
        | undefined;
      const optionalNamespace = optionalNamespaces
        ? optionalNamespaces[namespaceKey]
        : undefined;

      let chains: string[] | undefined =
        requiredNamespace === undefined ? undefined : requiredNamespace.chains;
      if (optionalNamespace && optionalNamespace.chains) {
        if (chains) {
          // merge chains from requiredNamespace & optionalNamespace, while avoiding duplicates
          chains = Array.from(new Set(chains.concat(optionalNamespace.chains)));
        } else {
          chains = optionalNamespace.chains;
        }
      }

      const accounts: string[] = [];
      chains?.map((chain) => {
        accounts.push(`${chain}:${address}`);
        return null;
      });
      const namespace: SessionTypes.Namespace = {
        accounts,
        chains: chains,
        methods:
          requiredNamespace === undefined ? [] : requiredNamespace.methods,
        events: requiredNamespace === undefined ? [] : requiredNamespace.events,
      };

      if (requiredNamespace && requiredNamespace.chains) {
        const _chainId = parseInt(requiredNamespace.chains[0].split(":")[1]);
        setSelectedNetworkOption({
          label: networksList[_chainId].name,
          value: _chainId,
        });
      }

      const session = await web3wallet?.approveSession({
        id: proposal.id,
        namespaces: {
          [namespaceKey]: namespace,
        },
      });
      setWeb3WalletSession(session);
      setIsConnected(true);
    },
    [web3wallet]
  );

  const handleSendTransaction = useCallback(
    async (id: number, params: any[], topic?: string) => {
      const txValue = params[0].value
        ? ethers.BigNumber.from(params[0].value).toString()
        : "0";

      setSendTxnData((data) => {
        const newTxn = {
          id: id,
          from: params[0].from,
          to: params[0].to,
          data: params[0].data,
          value: txValue,
        };

        if (data.some((d) => d.id === newTxn.id)) {
          return data;
        } else {
          return [newTxn, ...data];
        }
      });

      // If active smart wallet exists, create transaction for approval/execution
      if (activeWallet) {
        try {
          // Convert value properly using BigNumber
          const valueBigNumber = ethers.BigNumber.from(txValue);
          const valueHex = valueBigNumber.toHexString();

          await createTransaction({
            from: activeWallet.address,
            to: params[0].to,
            value: valueHex,
            data: params[0].data || "0x",
            method: defaultExecutionMethod,
          });

          toast({
            title: "Transaction Created",
            description: "Transaction added to approval queue",
            status: "info",
            isClosable: true,
          });
        } catch (error: any) {
          toast({
            title: "Transaction Creation Failed",
            description: error.message || "Failed to create transaction",
            status: "error",
            isClosable: true,
          });
        }
      }

      // Handle execution method
      if (defaultExecutionMethod === TransactionExecutionMethod.SIMULATION && tenderlyForkId.length > 0) {
        const { data: res } = await axios.post(
          "https://rpc.tenderly.co/fork/" + tenderlyForkId,
          {
            jsonrpc: "2.0",
            id: id,
            method: "eth_sendTransaction",
            params: params,
          }
        );
        console.log({ res });

        toast({
          title: "Txn Simulated on Tenderly",
          description: `Hash: ${res.result}`,
          status: "success",
          position: "bottom-right",
          duration: null,
          isClosable: true,
        });
      }

      // Respond to WalletConnect
      if (web3wallet && topic) {
        if (activeWallet && defaultExecutionMethod !== TransactionExecutionMethod.SIMULATION) {
          // For now, return error - actual execution will be handled through approval flow
          await web3wallet.respondSessionRequest({
            topic,
            response: {
              jsonrpc: "2.0",
              id: id,
              error: {
                code: 0,
                message: "Transaction queued for approval. Check Smart Wallet tab.",
              },
            },
          });
        } else {
          await web3wallet.respondSessionRequest({
            topic,
            response: {
              jsonrpc: "2.0",
              id: id,
              error: {
                code: 0,
                message: "Method not supported by Impersonator",
              },
            },
          });
        }
      }
    },
    [tenderlyForkId, web3wallet, activeWallet, createTransaction, defaultExecutionMethod, toast]
  );

  const onSessionRequest = useCallback(
    async (event: { topic: string; params: { request: any }; id: number }) => {
      const { topic, params, id } = event;
      const { request } = params;

      console.log("EVENT", "session_request", event);

      if (request.method === "eth_sendTransaction") {
        await handleSendTransaction(id, request.params, topic);
      } else {
        await web3wallet?.respondSessionRequest({
          topic,
          response: {
            jsonrpc: "2.0",
            id: id,
            error: {
              code: 0,
              message: "Method not supported by Impersonator",
            },
          },
        });
      }
    },
    [web3wallet, handleSendTransaction]
  );

  const onSessionDelete = () => {
    console.log("EVENT", "session_delete");

    reset();
  };

  const subscribeToEvents = useCallback(async () => {
    console.log("ACTION", "subscribeToEvents");

    if (web3wallet) {
      web3wallet.on("session_proposal", onSessionProposal);
      try {
        await web3wallet.core.pairing.pair({ uri });
      } catch (e) {
        console.error(e);
      }

      web3wallet.on("session_request", onSessionRequest);

      web3wallet.on("session_delete", onSessionDelete);
    }
  }, [handleSendTransaction, web3wallet]);

  useEffect(() => {
    if (web3wallet) {
      subscribeToEvents();
    }
    return () => {
      // Clean up event listeners
      if (web3wallet) {
        web3wallet.removeListener("session_proposal", onSessionProposal);
        web3wallet.removeListener("session_request", onSessionRequest);
        web3wallet.removeListener("session_delete", onSessionDelete);
      }
    };
  }, [web3wallet, subscribeToEvents]);

  const updateSession = async ({
    newChainId,
    newAddress,
  }: {
    newChainId?: number;
    newAddress?: string;
  }) => {
    let _chainId = newChainId || networkId;
    let _address = newAddress || address;

    if (web3wallet && web3WalletSession) {
      await web3wallet.emitSessionEvent({
        topic: web3WalletSession.topic,
        event: {
          name: _chainId !== networkId ? "chainChanged" : "accountsChanged",
          data: [_address],
        },
        chainId: `eip155:${_chainId}`,
      });
      setLoading(false);
    } else {
      setLoading(false);
    }
  };

  const updateAddress = async () => {
    if (selectedTabIndex === 0) {
      setLoading(true);
    } else {
      setIsIFrameLoading(true);
    }
    const { isValid, _address } = await resolveAndValidateAddress();

    if (isValid) {
      if (selectedTabIndex === 0) {
        updateSession({
          newAddress: _address,
        });
      } else {
        setIFrameAddress(_address);
        setIframeKey((key) => key + 1);
        setIsIFrameLoading(false);
      }
    }
  };

  const updateNetwork = (_networkId: number) => {
    setNetworkId(_networkId);

    if (selectedTabIndex === 0) {
      updateSession({
        newChainId: _networkId,
      });
    } else {
      setIframeKey((key) => key + 1);
    }
  };

  const killSession = async () => {
    console.log("ACTION", "killSession");

    if (web3wallet && web3WalletSession) {
      setWeb3WalletSession(undefined);
      setUri("");
      setIsConnected(false);

      try {
        await web3wallet.disconnectSession({
          topic: web3WalletSession.topic,
          reason: getSdkError("USER_DISCONNECTED"),
        });
      } catch (e) {
        console.error("killSession", e);
      }
    }
  };

  const reset = (persistUri?: boolean) => {
    setWeb3WalletSession(undefined);
    setIsConnected(false);
    if (!persistUri) {
      setUri("");
    }
    localStorage.removeItem("walletconnect");
  };

  return (
    <>
      {/* {process.env.NEXT_PUBLIC_GITCOIN_GRANTS_ACTIVE === "true" && (
        <NotificationBar />
      )} */}
      <NotificationBar />
      <Center mt="8" fontStyle={"italic"}>
        Connect to dapps as any ETH Address!
      </Center>
      <Container mt="2" mb="16" minW={["0", "0", "2xl", "2xl"]}>
        <Flex>
          <Spacer flex="1" />
          <TenderlySettings
            tenderlyForkId={tenderlyForkId}
            setTenderlyForkId={setTenderlyForkId}
          />
        </Flex>
        <AddressInput
          showAddress={showAddress}
          setShowAddress={setShowAddress}
          setAddress={setAddress}
          setIsAddressValid={setIsAddressValid}
          isAddressValid={isAddressValid}
          selectedTabIndex={selectedTabIndex}
          isConnected={isConnected}
          appUrl={appUrl}
          isIFrameLoading={isIFrameLoading}
          updateAddress={updateAddress}
        />
        <NetworkInput
          primaryNetworkOptions={primaryNetworkOptions}
          secondaryNetworkOptions={secondaryNetworkOptions}
          selectedNetworkOption={selectedNetworkOption}
          setSelectedNetworkOption={setSelectedNetworkOption}
        />
        <TabsSelect
          selectedTabIndex={selectedTabIndex}
          setSelectedTabIndex={setSelectedTabIndex}
        />
        {(() => {
          switch (selectedTabIndex) {
            case 0:
              return (
                <WalletConnectTab
                  uri={uri}
                  setUri={setUri}
                  isConnected={isConnected}
                  initWalletConnect={initWalletConnect}
                  loading={loading}
                  setLoading={setLoading}
                  reset={reset}
                  killSession={killSession}
                  web3WalletSession={web3WalletSession}
                />
              );
            case 1:
              return (
                <IFrameConnectTab
                  networkId={networkId}
                  initIFrame={initIFrame}
                  setInputAppUrl={setInputAppUrl}
                  inputAppUrl={inputAppUrl}
                  isIFrameLoading={isIFrameLoading}
                  appUrl={appUrl}
                  iframeKey={iframeKey}
                  iframeRef={iframeRef}
                  setIsIFrameLoading={setIsIFrameLoading}
                  showAddress={showAddress}
                />
              );
            case 2:
              return <BrowserExtensionTab />;
            case 3:
              return (
                <VStack spacing={6} mt={4} align="stretch">
                  <WalletManager />
                  {activeWallet && (
                    <>
                      <OwnerManagement />
                      <WalletBalance />
                      <TransactionBuilder />
                      <TransactionApproval />
                      <TransactionHistory />
                    </>
                  )}
                </VStack>
              );
          }
        })()}
        <Center>
          <TransactionRequests
            sendTxnData={sendTxnData}
            setSendTxnData={setSendTxnData}
            networkId={networkId}
          />
        </Center>
      </Container>
    </>
  );
}

export default Body;
