/* import { Buffer } from 'buffer';
globalThis.Buffer = Buffer; */
import { PostMessage } from './message';
import {
  IInitData,
  UserResponse,
  AccountInfo,
  UserResponseStatus,
  EndlessSignMessageInput,
  EndlessSignMessageOutput,
  EndlessSignAndSubmitTransactionInput,
  EndlessWalletTransactionType,
  UserRejection,
  NetworkInfo,
  ChainData,
} from './types';
// web and WebView
/// #if BUILD_PLATFORM !== 'MINIPROGRAM'
/// #endif
import { isApproveTx, isLuffa, isLuffaMiniProgram, isLuffaMiniProgramWebview, normalizeMessageForDisplay } from './utils';
import { TronWebOptions } from 'tronWeb/lib/esm/types';
import { TronSDKEvent, TronSDKEventPayload, TronSDKEventType, 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 interface TronRequestParams {
  method: 'eth_requestAccounts' | string;
  params?: any;
}
export { TronSDKEvent } from './message/types';
export { UserResponseStatus, EndlessSendTransactionType, EndlessWalletTransactionType } from './types';
export type { UserResponse, AccountInfo, EndlessSignAndSubmitTransactionInput } from './types';
export enum MethodName {
  CONNECT = 'connect',
  GETACCOUNT = 'getAccount',
  DISCONNECT = 'disconnect',
  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 LuffaTronSdk {
  static readonly version: string = '1.0.5';
  private static _instance: LuffaTronSdk;
  private message: PostMessage | null = null;
  private _metadata: Metadata = {} as Metadata;
  private _initData: IInitData = {} as IInitData;
  private accountAddress: string | null = null;
  private _wallet: any | null = null;

  static getIninData = (): IInitData => {
    if (LuffaTronSdk._instance) {
      return LuffaTronSdk._instance._initData;
    } else {
      return {} as IInitData;
    }
  };
  static getAccountAddress = () => {
    if (LuffaTronSdk._instance) {
      return LuffaTronSdk._instance.accountAddress;
    } else {
      return null;
    }
  };
  static setAccountAddress = (accountAddress: string | null) => {
    if (LuffaTronSdk._instance) {
      LuffaTronSdk._instance?._wallet?.setAddress(accountAddress);
      LuffaTronSdk._instance.accountAddress = accountAddress;
    }
  };

  constructor(initData: TronWebOptions & { network: string }) {
    if (LuffaTronSdk._instance) return LuffaTronSdk._instance;
    this.message = new PostMessage();
    this.getMetadata();
    this.initConfig(initData);
    LuffaTronSdk._instance = this;
  }

  private initConfig(initData: TronWebOptions & { network: string }) {
    this._initData.callbackWalletName = 'tronWallet';
    this._initData.network = initData.network;
    if (isLuffaMiniProgram()) {
      return;
    }
    // web and WebView
    /// #if BUILD_PLATFORM !== 'MINIPROGRAM'

    this._wallet = new window.TronWeb.TronWeb(initData);
    this._wallet.trx.sign = this.sendTransaction.bind(this);
    this._wallet.trx.signTransaction = this.sendTransaction.bind(this);

    const tronProvider = Object.freeze({
      isTronLink: false,
      request: async (params: TronRequestParams) => {
        switch (params.method) {
          case 'eth_requestAccounts':
            return this.connect();

          default:
            return { code: 4200, message: 'Unknown method called' };
        }
      },
      tronWeb: this._wallet,
      on: this.on,
      removeListener: this.off,
    });

    Object.defineProperty(globalThis, 'tronWeb', {
      value: this._wallet,
      writable: false,
      configurable: false,
      enumerable: true
    });

    Object.defineProperty(globalThis, 'tron', {
      value: tronProvider,
      writable: false,
      configurable: false,
      enumerable: true
    });
    /// #endif
  }

  async sendTransaction(params: any) {
    if (!this.accountAddress) {
      await this.connect();
    }
    console.log('this.accountAddress: ', this.accountAddress);
    if (typeof params === 'string') {
      return this.signMessage(params);
    }
    const { isApprove, spender } = isApproveTx(params);
    console.log('isApprove: ', isApprove, spender);
    if (isApprove) {
      params.to = spender;
      return this.signTransaction(params, MethodName.EVM_APPROVE)
    } else {
      return this.signTransaction(params)
    }
  }

  changeNetwork(chainData: ChainData) {
    this.message?.sendMessage({
      uuid: new Date().getTime().toString(),
      methodName: MethodName.NETWORK_CHANGE,
      metadata: this._metadata,
      data: {
        ...chainData,
      },
    });
  }

  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) => {
    this.message?.sendMessage(
      {
        uuid: new Date().getTime().toString(),
        methodName: data.method,
        metadata: this._metadata,
        data: data.data,
        initData: data?.initData,
      },
      callback
    );
  };

  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._wallet.setAddress(data.account);
            this.accountAddress = data.account;
            resolve([data.account]);
          } else {
            const res: UserResponse<AccountInfo> = {
              status: UserResponseStatus.REJECTED,
              message: data?.message || 'Wallet is not connected',
            };
            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._wallet.setAddress(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 TronSDKEventType>(methodName: K, callback: (payload: TronSDKEventPayload<K>) => void) => {
    if (this.message?.addListener) {
      this.message?.addListener(methodName, callback);
    }
  };
  off = <K extends TronSDKEventType>(methodName: K, callback?: (payload: TronSDKEventPayload<K>) => void) => {
    if (this.message?.removeListener) {
      this.message?.removeListener(methodName, callback);
    }
  };

  signAndSubmitTransaction = (params: any): Promise<any> => {
    console.log('signAndSubmitTransaction data: ', params);
    return new Promise(async (resolve) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.SIGN_AND_SUBMIT_TRANSACTION,
          metadata: this._metadata,
          data: params
        },
        (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 tron signature: ', signature);
          if (Array.isArray(signature) && signature.length > 0) {
            const result = {
              ...params,
              signature
            };
            resolve(result);
          } else {
            reject('Confirmation declined by user');
          }
        }
      );
    });
  };

  signMessage = (
    data: string,
    callback?: (data: unknown) => void
  ): Promise<UserResponse<any>> => {
    return new Promise(async (resolve) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.SIGN_MESSAGE,
          metadata: this._metadata,
          data: {
            message: normalizeMessageForDisplay(data)
          }
        },
        (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(TronSDKEvent.ACCOUNT_CHANGE, callback);
  };

  onNetworkChange = (callback: (data: NetworkInfo) => void) => {
    this.on(TronSDKEvent.NETWORK_CHANGE, callback);
  };
}
