import assert from "assert";
import React, {
  createContext,
  useContext,
  ReactNode,
  useState,
  useEffect,
  useCallback,
  useRef,
} from "react";
import { enqueueSnackbar } from "notistack";
import { useNavigate } from "react-router-dom";

import SignClient from "@walletconnect/sign-client";
import { WalletConnectModal } from "@walletconnect/modal";
import { SessionTypes } from "@walletconnect/types";
import { getSdkError } from "@walletconnect/utils";

const PROJECT_ID = process.env.REACT_APP_WALLET_CONNECT_ID;
assert(PROJECT_ID, "Wallet connect project id not provided");

interface ContextValue {
  connect: () => Promise<void>;
  session: SessionTypes.Struct | null;
  isSessionLoading: boolean;
  signClient: SignClient | undefined;
  checkPersistedState: (client: SignClient) => Promise<void>;
  disconnect: () => Promise<void>;
}

const walletConnectContext = createContext<ContextValue>({
  connect: () => Promise.resolve(),
  session: null,
  isSessionLoading: true,
  signClient: undefined,
  checkPersistedState: () => Promise.resolve(),
  disconnect: () => Promise.resolve(),
});

const web3Modal = new WalletConnectModal({
  projectId: PROJECT_ID,
  chains: [`eip155:${process.env.REACT_APP_ETHEREUM_MAINNET_CHAIN_ID}`],
});

export const WalletConnectProvider = ({
  children,
}: {
  children: ReactNode;
}) => {
  const [signClient, setSignClient] = useState<SignClient>();
  const [session, setSession] = useState<SessionTypes.Struct | null>(null);
  const [isSessionLoading, setIsSessionLoading] = useState(true);
  const isSignClientInitializing = useRef<boolean>(false);
  const navigate = useNavigate();

  const disconnect = useCallback(async () => {
    if (signClient && session) {
      await signClient.disconnect({
        topic: session.topic,
        reason: getSdkError("USER_DISCONNECTED"),
      });
    }

    setSession(null);
    setIsSessionLoading(false);
  }, [signClient, session]);

  const checkPersistedState = useCallback(async (client: SignClient) => {
    if (client.session.length) {
      const lastKeyIndex = client.session.keys.length - 1;
      const session = client.session.get(client.session.keys[lastKeyIndex]);
      setSession(session);
    }

    setIsSessionLoading(false);
  }, []);

  const subscribeToEvents = useCallback(
    async (client: SignClient) => {
      client.on("session_update", ({ topic, params }) => {
        const { namespaces } = params;
        const currentSession = client.session.get(topic);
        const updatedSession = { ...currentSession, namespaces };
        setSession(updatedSession);
        setIsSessionLoading(false);
      });

      client.on("session_delete", () => {
        setSession(null);
        setIsSessionLoading(false);
        navigate("/");
      });
    },
    [navigate]
  );

  const createClient = useCallback(async () => {
    isSignClientInitializing.current = true;
    const signClient = await SignClient.init({
      projectId: PROJECT_ID,
      metadata: {
        name: "Testnet onboarding app",
        description: "Testnet onboarding app",
        url: process.env.REACT_APP_WALLET_META_URL!,
        icons: ["https://avatars.githubusercontent.com/u/92608123"],
      },
    });

    setSignClient(signClient);
    await subscribeToEvents(signClient);
    await checkPersistedState(signClient);
    isSignClientInitializing.current = false;
  }, [checkPersistedState, subscribeToEvents]);

  const connect = async () => {
    if (!signClient) {
      throw Error("SignClient does not exist");
    }

    const proposalNamespace = {
      eip155: {
        methods: ["personal_sign"],
        chains: [`eip155:${process.env.REACT_APP_ETHEREUM_MAINNET_CHAIN_ID}`],
        events: [],
      },
      cosmos: {
        methods: [
          "cosmos_sendTransaction",
        ],
        chains: [`cosmos:${process.env.REACT_APP_LACONICD_CHAIN_ID}`],
        events: [],
      },
    };

    const { uri, approval } = await signClient.connect({
      requiredNamespaces: proposalNamespace,
    });

    if (uri) {
      web3Modal.openModal({ uri });
      try {
        const session = await approval();
        setSession(session);
        setIsSessionLoading(false);
      } catch (error) {
        enqueueSnackbar("User rejected pairing request", { variant: "error" });
        console.log(error);
      }
      web3Modal.closeModal();
    }

  };

  useEffect(() => {
    if (!signClient && !isSignClientInitializing.current) {
      createClient();
    }
  }, [signClient, createClient]);

  return (
    <walletConnectContext.Provider
      value={{
        connect,
        session,
        isSessionLoading,
        signClient,
        checkPersistedState,
        disconnect,
      }}
    >
      {children}
    </walletConnectContext.Provider>
  );
};

export const useWalletConnectContext = () => {
  const { connect, session, signClient, isSessionLoading, checkPersistedState, disconnect } =
    useContext(walletConnectContext);

  return {
    connect,
    session,
    isSessionLoading,
    signClient,
    checkPersistedState,
    disconnect,
  };
};
