import type { AppInfo, GatewayConfig, PermissionType } from 'arconnect';
import { ArweaveWebWallet } from 'arweave-wallet-connector';
import { ArweaveInterface } from 'arweave-wallet-connector/lib/Arweave';
import React, {
  useState,
  useEffect,
  useContext,
  createContext,
  ReactNode,
} from 'react';
import { calculateColorFromAddress } from './helpers'

import './index.css';
import ArconnectLogo from './icons/arconnect/logo';
import ArweaveAppLogo from './icons/arweave.app/logo';
import WalletIcon from './icons/wallet';

// note: arconnect works natively with the APIs of window.arweaveWallet
export type supportedWallets =
  | 'arconnect'
  | 'arweave.app'
  | 'window.arweaveWallet';

export type AlgorithmInterface =
  | AlgorithmIdentifier
  | RsaPssParams
  | EcdsaParams;

export interface options {
  algorithm: string;
  hash: string;
  salt?: string;
}

type BufferEncoding =
  | 'ascii'
  | 'utf8'
  | 'utf-8'
  | 'utf16le'
  | 'ucs2'
  | 'ucs-2'
  | 'base64'
  | 'base64url'
  | 'latin1'
  | 'binary'
  | 'hex';

export interface createSignatureInterface {
  data: string;
  signatureParams?: AlgorithmInterface;
  returnType?: 'Uint8Array' | BufferEncoding;
  sigType?: 'new' | 'old';
}

// export type encryptInterface = (
//   options: {
//     name: string;
//     hash?: string;
//     salt?: string;
//   },
//   data: BufferSource
// ) => Promise<Uint8Array>;

// export type decryptInterface = (
//   data: Uint8Array,
//   algorithm: options
// ) => Promise<string>;

// export interface SimpleConnectArgs {
//   permissions: PermissionType[],
//   AppInfo?: AppInfo,
//   gatewayConfig?: GatewayConfig,
//   walletChoice?: supportedWallets
// }

// export type SimpleConnectInterface = (args: SimpleConnectArgs) => Promise<void>;

export interface arweaveConnectInterface {
  permissions: PermissionType[];
  AppInfo?: AppInfo;
  gatewayConfig?: GatewayConfig;
  walletChoice?: supportedWallets;
}

export interface arweaveContextInterface {
  api: any | ArweaveInterface;
  walletPermissions: PermissionType[];
  walletConnected: boolean;
  walletType: supportedWallets | undefined;
  address: string;
  searchingWallet: boolean;
  getActiveAddress: () => Promise<string>;
  arweaveConnect: ({
    permissions,
    AppInfo,
    gatewayConfig,
    walletChoice,
  }: arweaveConnectInterface) => Promise<{ api: any }>;
  arweaveDisconnect: () => Promise<void>;
  getPublicKey: () => Promise<string>;
  createSignature: ({
    data,
    signatureParams,
    returnType,
    sigType,
  }: createSignatureInterface) => Promise<any>;
}

export const defaultSignatureParams = { name: 'RSA-PSS', saltLength: 32 };
export const defaultAlgorithmParams = {
  name: 'RSA-PSS',
  hash: 'sha256',
  saltLength: 32,
};

export const ArweavePermissions: PermissionType[] = [
  'ACCESS_ADDRESS',
  'ACCESS_PUBLIC_KEY',
  'ACCESS_ALL_ADDRESSES',
  'SIGN_TRANSACTION',
  'ENCRYPT',
  'DECRYPT',
  'SIGNATURE',
  'ACCESS_ARWEAVE_CONFIG',
  'DISPATCH',
];

export { PermissionType };

export const shortenAddress = (address: string, maxLength = 20) => {
  // to avoid trimming small names by default
  if (address.length < maxLength) return address;

  return (
    address.substring(0, maxLength / 2) +
    '...' +
    address.substring(address.length - maxLength / 2, address.length)
  );
};

export const ArweaveContext = createContext<arweaveContextInterface>(
  {} as arweaveContextInterface
);

export function useArweave(): arweaveContextInterface {
  const useArweaveContext: arweaveContextInterface = useContext(ArweaveContext);

  if (useArweaveContext === null) {
    throw new Error(
      'useArweave() can only be used inside of <ArweaveProvider />, ' +
        'please declare it at a higher level.'
    );
  }

  return useArweaveContext;
}

export async function getWalletPermissions(arweaveWallet) {
  try {
    return await arweaveWallet.getPermissions();
  } catch {
    return [];
  }
}

export async function getActiveAddress() {
  try {
    if (window.arweaveWallet) {
      const addy = await window.arweaveWallet.getActiveAddress();
      return addy;
    } else {
      return null;
    }
  } catch (e) {
    return null;
  }
}

export type ArweaveProviderProps = {
  children: any;
  permissions?: PermissionType[];
  AppInfo?: AppInfo;
  gatewayConfig?: GatewayConfig;
  defaultWallet?: supportedWallets;
};

export const ArweaveProvider = ({
  children,
  permissions,
  AppInfo,
  gatewayConfig={ host: '443', port: 88, protocol: 'https' },
}: ArweaveProviderProps) => {
  const [api, setApi] = useState<any | ArweaveInterface>(undefined);
  const [arweaveAppAPI, setArweaveAppAPI] = useState<any>(undefined);
  const [walletType, setWalletType] = useState<supportedWallets | undefined>();
  const [walletConnected, setWalletConnected] = useState<boolean>(false);
  const [searchingWallet, setSearchingWallet] = useState<boolean>(true);
  const [address, setAddress] = useState<string>('');
  const [walletPermissions, setWalletPermissions] = useState<PermissionType[]>(
    permissions || []
  );

  const config = gatewayConfig;
  const appInfo = AppInfo;
  const perms = permissions;

  const arweaveConnect = async ({
    permissions={...perms},
    AppInfo={...appInfo},
    gatewayConfig={...config},
    walletChoice="arconnect",
  }: arweaveConnectInterface) => {
    try {
      // check for arconnect / any other arweave-based wallet
      let arweaveWallet: any = window.arweaveWallet;
      if (
        !arweaveWallet ||
        (walletChoice !== 'arconnect' &&
          walletChoice !== 'window.arweaveWallet')
      ) {
        console.log('attempting to connect to arweave.app...');
        try {
          const wallet = new ArweaveWebWallet(AppInfo);
          wallet.setUrl('arweave.app');
          setArweaveAppAPI(wallet);
          wallet
            .connect({ permissions, AppInfo, gatewayConfig })
            .then(async () => {
              arweaveWallet = wallet.namespaces.arweaveWallet;
              setApi(arweaveWallet);
              setArweaveAppAPI(wallet);
              setWalletPermissions(permissions);
              const address = await arweaveWallet.getActiveAddress();
              setWalletType('arweave.app');
              setWalletConnected(true);
              setAddress(address);
              return { api: arweaveWallet };
            });
        } catch (error) {
          console.log(error);
        }
        return { api: undefined };
      } else {
        if (
          permissions.length === 0 ||
          !permissions?.includes('ACCESS_ADDRESS')
        )
          throw new Error('ACCESS_ADDRESS permission is required to connect');

        await arweaveWallet.connect(permissions, AppInfo, gatewayConfig);
        setApi(arweaveWallet);
        setWalletPermissions(permissions);
        const address = await arweaveWallet.getActiveAddress();
        setAddress(address);
        setWalletConnected(true);
        setWalletType('arconnect');
        return { api: arweaveWallet };
      }
    } catch (Error) {
      console.error(Error);
    }
    return { api: undefined };
  };

  const arweaveDisconnect = async () => {
    try {
      try {
        await api.disconnect();
      } catch (e) {
        await window.arweaveWallet.disconnect();
      }
      setWalletConnected(false);
      setAddress(undefined);
      setWalletPermissions([]);
      setArweaveAppAPI(undefined);
      setWalletType(undefined);
    } catch (Error) {
      console.error(Error);
    }
  };

  const getPublicKey = async () => {
    try {
      return await api.getActivePublicKey();
    } catch (Error) {
      console.error(Error);
      return '';
    }
  };

  // example of params for signature generation:
  // data: new TextEncoder().encode("Hello world!");
  // signatureParams: defaultSignatureParams
  // return type: signature type
  // sigType: whether arconnect signature will use signMessage or signature method

  const createSignature = async ({
    data,
    signatureParams = defaultSignatureParams,
    returnType = 'base64',
    sigType = 'new',
  }: createSignatureInterface) => {
    try {
      let signature = undefined;
      const encoder = new TextEncoder();
      const encodedString = encoder.encode(data);

      if (walletType === 'arweave.app') {
        signature = await arweaveAppAPI.signMessage(encodedString, {
          hashAlgorithm: 'SHA-256',
        });
      } else {
        if (sigType === 'new') {
          signature = await api.signMessage(encodedString);
        } else {
          signature = await api.signature(encodedString, signatureParams);
        }
      }
      if (!signature) throw new Error('Signature generation failed');
      if (returnType === 'Uint8Array') return signature;
      else {
        return Buffer.from(signature).toString(returnType);
      }
    } catch (Error) {
      console.error(Error);
      return undefined;
    }
  };

  useEffect(() => {
    setSearchingWallet(true);
    getActiveAddress().then((addy) => {
      if (addy) {
        setWalletConnected(true);
        setSearchingWallet(false);
        setAddress(addy);
        const wallet = new ArweaveWebWallet(AppInfo);
        wallet.setUrl('arweave.app');
        setArweaveAppAPI(wallet);
        let arweaveWallet = window.arweaveWallet;
        setApi(arweaveWallet);
      } else {
        setWalletConnected(false);
        setSearchingWallet(false);
      }
    });
  }, []);

  useEffect(() => {
    let apiInjected = false;

    // double check if arconnect was added
    setTimeout(() => {
      if (apiInjected || api) return;
      //loadedEvent(); //Disables window pop up
    }, 2500);
  }, [api]);

  const value = {
    api,
    walletPermissions,
    walletConnected,
    walletType,
    address,
    searchingWallet,
    arweaveConnect,
    arweaveDisconnect,
    getActiveAddress,
    getPublicKey,
    createSignature,
  };

  return (
    <ArweaveContext.Provider {...{ value }}>{children}</ArweaveContext.Provider>
  );
};

interface ConnectButtonInterface {
  walletChoice: supportedWallets;
  permissions: PermissionType[];
  className?: string;
  onConnectCallback?: () => void;
  gatewayConfig?: GatewayConfig;
  AppInfo?: {
    name: string;
    logo: string;
  };
  children?: ReactNode;
}

export const ConnectButton = ({
  walletChoice,
  permissions,
  className,
  onConnectCallback,
  AppInfo,
  gatewayConfig,
  children,
}: ConnectButtonInterface) => {
  const { arweaveConnect } = useArweave();

  const connect = async () => {
    const val = await arweaveConnect({
      walletChoice,
      permissions,
      AppInfo,
      gatewayConfig,
    });
    console.log(val);
    onConnectCallback();
  };
  const classAlt =
    'text-black dark:text-white bg-white dark:bg-black border-black dark:border-white rounded-lg';

  return (
    <button style={{padding: "8px 16px"}} onClick={connect} className={className || classAlt}>
      {children}
    </button>
  );
};

interface DisconnectButtonProps {
  className?: string;
}

export const DisconnectButton = ({ className }: DisconnectButtonProps) => {
  const { address, arweaveDisconnect } = useArweave();
  const classAlt =
    'text-black dark:text-white bg-white dark:bg-black border-black dark:border-white border-2 rounded-lg px-2 py-1';
  return (
    <button className={className || classAlt} onClick={arweaveDisconnect}>
      <div className="w-8 h-8" style={{ backgroundColor: calculateColorFromAddress(address) }}></div>
      <span>{shortenAddress(address, 8)}</span>
    </button>
  );
};

interface ArweaveModalProps {
  isOpen: boolean;
  onClose: () => void;
}

export const ArweaveModal = ({ isOpen, onClose }: ArweaveModalProps) => {
  if (!isOpen) return null;

  const permissions: PermissionType[] = [
    'ACCESS_ADDRESS',
    'ACCESS_ALL_ADDRESSES',
    'ACCESS_ARWEAVE_CONFIG',
    'ACCESS_PUBLIC_KEY',
    'DECRYPT',
    'DISPATCH',
    'ENCRYPT',
    'SIGNATURE',
    'SIGN_TRANSACTION',
  ];
  const className = 'rounded-lg border-white border-2 bg-[#111111] px-4 py-3 text-white';

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-content" onClick={(e) => e.stopPropagation()}>
        <ConnectButton
          walletChoice="arconnect"
          {...{ permissions, className, onConnectCallback: onClose }}
        >
          <div className="flex items-center" style={{ gap: '6px' }}>
            <ArconnectLogo outerFill="white" innerFill="black" width={"40px"} height={"40px"} />
            <span>Connect with Arconnect</span>
          </div>
        </ConnectButton>
        <ConnectButton
          walletChoice="arweave.app"
          {...{ permissions, className, onConnectCallback: onClose }}
        >
          <div className="flex items-center" style={{ gap: '6px' }}>
            <WalletIcon width={"40px"} height={"40px"} />
            <span>Connect with window.arweaveWallet</span>
          </div>
        </ConnectButton>
        <ConnectButton
          walletChoice="window.arweaveWallet"
          {...{ permissions, className, onConnectCallback: onClose }}
        >
          <div className="flex items-center" style={{ gap: '6px' }}>
            <ArweaveAppLogo stroke="white" width={"40px"} height={"40px"} />
            <span>Connect with an Arweave wallet</span>
          </div>
        </ConnectButton>
        <button
          style={{
            position: 'absolute',
            top: '4px',
            right: '4px',
            rotate: '45deg',
          }}
          onClick={onClose}
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            strokeWidth={1.5}
            stroke="currentColor"
            className="w-6 h-6"
          >
            <path
              strokeLinecap="round"
              strokeLinejoin="round"
              d="M12 4.5v15m7.5-7.5h-15"
            />
          </svg>
        </button>
      </div>
    </div>
  );
};

interface ArweaveConnectButtonProps {
  connectButtonClass?: string;
  disconnectButtonClass?: string;
}
export const ArweaveConnectButton = ({
  disconnectButtonClass,
  connectButtonClass,
}: ArweaveConnectButtonProps) => {
  const [isOpen, setIsOpen] = useState(false);
  const { walletConnected } = useArweave();

  return (
    <div>
      {walletConnected ? (
        <DisconnectButton className={disconnectButtonClass} />
      ) : (
        <button
          className={`${
            connectButtonClass ? connectButtonClass : 'connect-button'
          } `}
          onClick={() => setIsOpen(true)}
        >
          Connect Arweave Wallet
        </button>
      )}

      <ArweaveModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
    </div>
  );
};
