import assert from "assert";
import React, {
  createContext,
  useContext,
  ReactNode,
  useState,
  useEffect,
  useCallback,
} from "react";
import { SnackbarProvider, 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;
  signClient: SignClient | undefined;
  checkPersistedState: (client: SignClient) => Promise<void>;
  disconnect: () => Promise<void>;
}

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

const web3Modal = new WalletConnectModal({
  projectId: PROJECT_ID,
  chains: ["eip155:1"],
});

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

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

    setSession(null);
  }, [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);
    }
  }, []);

  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);
      });

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

  const createClient = useCallback(async () => {
    const signClient = await SignClient.init({
      projectId: PROJECT_ID,
      metadata: {
        name: "Urbit onboarding app",
        description: "Urbit onboarding app",
        url: "localhost:3000",
        icons: ["https://avatars.githubusercontent.com/u/5237680?s=200&v=4"],
      },
    });

    setSignClient(signClient);
    await subscribeToEvents(signClient);
    await checkPersistedState(signClient);
  }, [checkPersistedState, subscribeToEvents])

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

    const proposalNamespace = {
      eip155: {
        methods: ["personal_sign"],
        chains: ["eip155:1"],
        events: [],
      },
      cosmos: {
        methods: ["cosmos_signDirect", "cosmos_signAmino"],
        chains: ["cosmos:cosmoshub-4"],
        events: [],
      },
    };

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

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

  useEffect(() => {
    if (!signClient) {
      createClient();
    }
  }, [signClient, createClient]);

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

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

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