/* import { Buffer } from 'buffer';
globalThis.Buffer = Buffer; */
import { PostMessage } from './message';
import { v4 as uuidv4 } from 'uuid';
import {
  IInitData,
  UserResponse,
  AccountInfo,
  UserResponseStatus,
  UserRejection,
  NetworkInfo,
  ChainData,
} from './types';
// web and WebView
/// #if BUILD_PLATFORM !== 'MINIPROGRAM'
/// #endif
import { getChain, getChainIdByName, isApproveTx, isLuffa, isLuffaMiniProgram, isLuffaMiniProgramWebview, normalizeMessageForDisplay } from './utils';
import { EvmSDKEvent, EvmSDKEventPayload, EvmSDKEventType, IRequestData } from './message/types';
export { isLuffa, isLuffaMiniProgram, isLuffaMiniProgramWebview } from './utils';
export interface Metadata {
  title: string;
  url: string;
  origin: string;
  icon: string;
  gameId: string;
  userId: string;
  walletAddress: string;
}
export { EvmSDKEvent } from './message/types';
export { UserResponseStatus } from './types';
export type { UserResponse, AccountInfo } from './types';
export enum MethodName {
  CONNECT = 'connect',
  GETACCOUNT = 'getAccount',
  DISCONNECT = 'disconnect',
  CURRENT_CHAIN = 'currentChain',
  NETWORK_CHANGE = 'luffa_switchChain',
  SIGN_MESSAGE = 'signMessage',
  SEND_TRANSACTION = 'sendTransaction',
  SIGN_AND_SUBMIT_TRANSACTION = 'signAndSubmitTransaction',
  SIGN_TRANSACTION = 'signTransaction',
  SIGN_BUILD_TRANSACTION = 'signBuildTransaction',
  EVM_APPROVE = 'evmApprove',
  ACCOUNT_CHANGE = 'accountChange',
}
export class LuffaEvmSdk {
  static readonly version: string = '1.0.7';
  private static _instance: LuffaEvmSdk;
  private message: PostMessage | null = null;
  private _metadata: Metadata = {} as Metadata;
  private _initData: IInitData = {} as IInitData;
  private accountAddress: string | null = null;

  static getIninData = (): IInitData => {
    if (LuffaEvmSdk._instance) {
      return LuffaEvmSdk._instance._initData;
    } else {
      return {} as IInitData;
    }
  };
  static getAccountAddress = () => {
    if (LuffaEvmSdk._instance) {
      return LuffaEvmSdk._instance.accountAddress;
    } else {
      return null;
    }
  };
  static setAccountAddress = (accountAddress: string | null) => {
    if (LuffaEvmSdk._instance) {
      LuffaEvmSdk._instance.accountAddress = accountAddress;
    }
  };

  constructor(initData: IInitData) {
    if (LuffaEvmSdk._instance) return LuffaEvmSdk._instance;
    this.message = new PostMessage();
    this.getMetadata();
    this.initConfig(initData);
    LuffaEvmSdk._instance = this;
  }

  private initConfig(initData: IInitData) {
    this._initData.callbackWalletName = 'evmWallet';
    this._initData.network = initData.network;
    const evmProvider = Object.freeze({
      isMetaMask: false,
      request: this.request,
      on: this.on,
      removeListener: this.off,
    });
    Object.defineProperty(globalThis, 'ethereum', {
      value: evmProvider,
      writable: false,
    });
    function announceProvider() {
      const info = {
        uuid: uuidv4(),
        name: 'LuffaEvmWallet',
        icon: '',
        rdns: 'org.luffa.wallet',
      };
      window.dispatchEvent(
        new CustomEvent('eip6963:announceProvider', {
          detail: Object.freeze({ info, provider: evmProvider }),
        })
      );
    }

    window.addEventListener('eip6963:requestProvider', () => {
      announceProvider();
    });

    announceProvider();
  }

  async sendTransaction(params: any) {
    if (!this.accountAddress) {
      const res = await this.connect();
      this.accountAddress = res[0];
    }
    console.log('sendTransaction params: ', params);
    const { isApprove, spender } = isApproveTx(params.params[0].data);
    console.log('isApprove: ', isApprove, spender);
    if (isApprove) {
      params.to = spender;
      return this.signAndSubmitTransaction(params, MethodName.EVM_APPROVE);
    } else {
      return this.signAndSubmitTransaction(params);
    }
  }

  private getMetadata() {
    if (!window) {
      return;
    }
    const iconLink = document.querySelector('link[rel="icon"]') || document.querySelector('link[rel="shortcut icon"]');
    let iconUrl = iconLink?.getAttribute('href') || '';
    if (iconUrl && !iconUrl.startsWith('http')) {
      iconUrl = new URL(iconUrl, window.location.origin).href;
    }

    this._metadata.title = window.document.title;
    this._metadata.url = window.location.href;
    this._metadata.origin = window.location.origin;
    this._metadata.icon = iconUrl;
  }

  request = (data: IRequestData, callback?: (data: unknown) => void) => {
    switch (data.method) {
      case 'eth_requestAccounts':
        return this.connect();
      case 'eth_accounts':
        return this.getAccount();
      case 'eth_chainId':
        return this.currentChain();
      case 'wallet_switchEthereumChain':
        return this.luffa_switchChain(data);
      case 'eth_sendTransaction':
        return this.sendTransaction(data);
      case 'personal_sign':
        return this.signMessage(data);
      case 'wallet_revokePermissions':
        return this.disconnect();

      default: {
        throw new Error('Unsupported method: ' + data.method);
      }
    }
  };

  luffa_switchChain = (data: IRequestData): Promise<string | { status: string }> => {
    return new Promise((resolve) => {
      const network = getChain(data?.params[0]?.chainId);

      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.NETWORK_CHANGE,
          metadata: this._metadata,
          data: { targetNet: network },
        },
        (res) => {
          if (res?.network) {
            this._initData.network = res.network;
          }
          const result = {
            ...res,
          };
          resolve(result);
        }
      );
    });
  };

  currentChain = (): Promise<string | { status: string }> => {
    return new Promise((resolve) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.CURRENT_CHAIN,
          metadata: this._metadata,
          data: {},
        },
        (res) => {
          if (res?.network) {
            this._initData.network = res?.network;
            const result = `0x${getChainIdByName(res?.network)?.toString(16)}`;
            resolve(result);
          } else {
            const result = { status: 'Rejected' };
            resolve(result);
          }
        }
      );
    });
  };

  getAccount = (): Promise<string[]> => {
    return new Promise((resolve) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.GETACCOUNT,
          metadata: this._metadata,
          data: {},
        },
        (data) => {
          if (data?.account) {
            this.accountAddress = data.account;
            resolve([data.account]);
          } else {
            resolve([]);
          }
        }
      );
    });
  };

  connect = (): Promise<[string]> => {
    return new Promise((resolve, reject) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.CONNECT,
          metadata: this._metadata,
          data: {},
        },
        (data) => {
          let res;
          if (data?.account) {
            this.accountAddress = data.account;
            res = [data.account] as [string];
            resolve(res);
          } else {
            res = {
              code: 4001,
              message: 'User rejected the request.',
            };
            reject(res);
          }
        }
      );
    });
  };

  disconnect = (callback?: (data: unknown) => void): Promise<void> => {
    return new Promise((resolve, reject) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.DISCONNECT,
          metadata: this._metadata,
          data: {},
        },
        (data: unknown) => {
          this.accountAddress = null;
          if (callback) callback(data);
          resolve();
        }
      );
    });
  };

  on = <K extends EvmSDKEventType>(methodName: K, callback: (payload: EvmSDKEventPayload<K>) => void) => {
    if (this.message?.addListener) {
      this.message?.addListener(methodName, callback);
    }
  };
  off = <K extends EvmSDKEventType>(methodName: K, callback?: (payload: EvmSDKEventPayload<K>) => void) => {
    if (this.message?.removeListener) {
      this.message?.removeListener(methodName, callback);
    }
  };

  signAndSubmitTransaction = async (
    data: IRequestData,
    methodName = MethodName.SIGN_AND_SUBMIT_TRANSACTION
  ): Promise<any> => {
    if (!this.accountAddress) {
      const res = await this.connect();
      this.accountAddress = res[0];
    }
    return new Promise(async (resolve) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: methodName,
          metadata: this._metadata,
          data: data.params[0],
        },
        (res) => {
          if (res?.hash) {
            resolve(res.hash);
          } else {
            resolve({
              code: 4001,
              message: res?.message,
            });
          }
        }
      );
    });
  };

  signTransaction = (params: any, methodName = MethodName.SIGN_BUILD_TRANSACTION): Promise<any> => {
    return new Promise(async (resolve, reject) => {
      const data: { raw_data_hex: string; to?: string } = {
        raw_data_hex: params.raw_data_hex,
      };
      if (params.to) {
        data.to = params.to;
      }
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName,
          metadata: this._metadata,
          data,
        },
        (res) => {
          const signature = res?.signature?.split(',') || [];
          console.log('luffa evm signature: ', signature);
          if (Array.isArray(signature) && signature.length > 0) {
            const result = {
              ...params,
              signature,
            };
            resolve(result);
          } else {
            reject('Confirmation declined by user');
          }
        }
      );
    });
  };

  signMessage = (
    data: IRequestData,
    callback?: (data: unknown) => void
  ): Promise<UserResponse<any>> => {
    return new Promise(async (resolve) => {
      if (!this.accountAddress) {
        await this.connect();
      }
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.SIGN_MESSAGE,
          metadata: this._metadata,
          data: {
            message: normalizeMessageForDisplay(data.params[0])
          }
        },
        (res) => {
          console.log('signMessage res: ', res);
          if (res?.signature) {
            const result: UserResponse<any> = {
              status: UserResponseStatus.APPROVED,
              args: res,
            };
            resolve(result);
          } else {
            const result: UserRejection = { status: UserResponseStatus.REJECTED, message: res?.message };
            resolve(result);
          }
          if (callback) callback(res);
        }
      );
    });
  };

  onAccountChange = (callback: (data: AccountInfo) => void) => {
    this.on(EvmSDKEvent.ACCOUNT_CHANGE, callback);
  };

  onNetworkChange = (callback: (data: NetworkInfo) => void) => {
    this.on(EvmSDKEvent.NETWORK_CHANGE, callback);
  };
}
