import { PostMessage } from './message';
import type { IRequestData } from './message/types';
import type { EndLessSDKEventType, EndLessSDKEventPayload } from './message/types';
import { EndLessSDKEvent } from './message/types';
import {
  IInitData,
  UserResponse,
  AccountInfo,
  UserResponseStatus,
  EndlessSignMessageInput,
  EndlessSignMessageOutput,
  EndlessSignAndSubmitTransactionInput,
  EndlessWalletTransactionType,
  UserRejection,
  NetworkInfo,
} from './types';
// web and WebView
/// #if BUILD_PLATFORM !== 'MINIPROGRAM'
import {
  Endless,
  EndlessConfig,
} from '@endlesslab/endless-ts-sdk';
/// #endif
import { isLuffa, isLuffaMiniProgram, isLuffaMiniProgramWebview } from './utils';
export { isLuffa, isLuffaMiniProgram, isLuffaMiniProgramWebview } from './utils';
export interface Metadata {
  title: string;
  url: string;
  origin: string;
  icon: string;
  gameId: string;
  userId: string;
  walletAddress: string;
}
export { EndLessSDKEvent } 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 = 'switchNetwork',
  SIGN_MESSAGE = 'signMessage',
  SEND_TRANSACTION = 'sendTransaction',
  SIGN_AND_SUBMIT_TRANSACTION = 'signAndSubmitTransaction',
  SIGN_BUILD_TRANSACTION = 'signBuildTransaction',
  ACCOUNT_CHANGE = 'accountChange',
}
export class EndlessLuffaSdk {
  static readonly version: string = '1.0.5';
  private static _instance: EndlessLuffaSdk;
  private message: PostMessage | null = null;
  private _metadata: Metadata = {} as Metadata;
  private _initData: IInitData = {} as IInitData;
  private _endless: any | null = null;
  private _endlessConfig: EndlessConfig | null = null;
  private accountAddress: string | null = null;

  static getIninData = (): IInitData => {
    if (EndlessLuffaSdk._instance) {
      return EndlessLuffaSdk._instance._initData;
    } else {
      return {} as IInitData;
    }
  };
  static getAccountAddress = () => {
    if (EndlessLuffaSdk._instance) {
      return EndlessLuffaSdk._instance.accountAddress;
    } else {
      return null;
    }
  };
  static setAccountAddress = (accountAddress: string | null) => {
    if (EndlessLuffaSdk._instance) {
      EndlessLuffaSdk._instance.accountAddress = accountAddress;
    }
  };

  constructor(initData: IInitData) {
    if (EndlessLuffaSdk._instance) return EndlessLuffaSdk._instance;
    this.message = new PostMessage();
    this.initWalletEvent();
    this.getMetadata();
    this.initConfig(initData);
    EndlessLuffaSdk._instance = this;
  }

  private initConfig(initData: IInitData) {
    if (isLuffaMiniProgram()) {
      this._initData.network = initData.network;
      return;
    }
    // web and WebView
    /// #if BUILD_PLATFORM !== 'MINIPROGRAM'
    this._initData.network = initData.network;
    this._endlessConfig = new EndlessConfig({
      network: initData.network as unknown as any,
      miniprogram: initData.miniprogram
    });

    this._endless = new Endless(this._endlessConfig);
    /// #endif
  }

  private initWalletEvent() {
    this.on(EndLessSDKEvent.NETWORK_CHANGE, (payload) => {
      this.initConfig({
        network: payload.name,
      });
    });
  }

  changeNetwork(initData: IInitData) {
    this.message?.sendMessage({
      uuid: new Date().getTime().toString(),
      methodName: MethodName.NETWORK_CHANGE,
      metadata: this._metadata,
      data: {
        ...this._initData,
        ...initData,
      },
    });
  }

  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<UserResponse<AccountInfo>> => {
    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;
            const res: UserResponse<AccountInfo> = {
              status: UserResponseStatus.APPROVED,
              args: { ...data },
            };
            resolve(res);
          } else {
            const res: UserResponse<AccountInfo> = {
              status: UserResponseStatus.REJECTED,
              message: data?.message || 'Wallet is not connected',
            };
            resolve(res);
          }
        }
      );
    });
  };

  connect = (callback?: (data: AccountInfo) => void): Promise<UserResponse<AccountInfo>> => {
    return new Promise((resolve) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.CONNECT,
          metadata: this._metadata,
          data: {},
        },
        (data) => {
          if (data.account) {
            this.accountAddress = data.account;
            const res: UserResponse<AccountInfo> = {
              status: UserResponseStatus.APPROVED,
              args: { ...data },
            };
            resolve(res);
          } else {
            const res: UserResponse<AccountInfo> = { status: UserResponseStatus.REJECTED, message: data?.message };
            resolve(res);
          }
          if (callback) callback({ ...data });
        }
      );
    });
  };

  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();
        }
      );
    });
  };

  signMessage = (
    data: EndlessSignMessageInput,
    callback?: (data: unknown) => void
  ): Promise<UserResponse<EndlessSignMessageOutput>> => {
    return new Promise((resolve) => {
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.SIGN_MESSAGE,
          metadata: this._metadata,
          data: data,
        },
        (res) => {
          console.log('signMessage res: ', res);
          if (res.signature) {
            const result: UserResponse<EndlessSignMessageOutput> = {
              status: UserResponseStatus.APPROVED,
              args: {
                fullMessage: data.message,
                signature: res.signature,
                publicKey: res.publicKey,
                nonce: '',
                message: data.message,
                prefix: 'Endless',
              },
            };
            resolve(result);
          } else {
            const result: UserRejection = { status: UserResponseStatus.REJECTED, message: res?.message };
            resolve(result);
          }
          if (callback) callback(res);
        }
      );
    });
  };

  on = <K extends EndLessSDKEventType>(methodName: K, callback: (payload: EndLessSDKEventPayload<K>) => void) => {
    if (this.message?.addListener) {
      this.message?.addListener(methodName, callback);
    }
  };
  off = <K extends EndLessSDKEventType>(methodName: K, callback?: (payload: EndLessSDKEventPayload<K>) => void) => {
    if (this.message?.removeListener) {
      this.message?.removeListener(methodName, callback);
    }
  };

  signAndSubmitTransaction = (data: EndlessSignAndSubmitTransactionInput): Promise<UserResponse<{ hash: string }>> => {
    return new Promise(async (resolve) => {
      if (!this.accountAddress) {
        const result: UserRejection = { status: UserResponseStatus.REJECTED, message: 'Wallet not linked' };
        resolve(result);
        return;
      }
      let transaction;
      if (isLuffa() || isLuffaMiniProgramWebview()) {
        if (!this._endless) {
          const result: UserRejection = { status: UserResponseStatus.REJECTED, message: 'Wallet not linked' };
          resolve(result);
          return;
        }
        transaction = await this._endless.transaction.build.simple({
          sender: this.accountAddress,
          data: data.payload,
          options: {
            ...(data.options || {}),
          },
        });
        transaction = transaction.bcsToHex().toString();
      } else if (isLuffaMiniProgram()) {
        // TODO MiniProgram build transaction
        transaction = data;
      }
      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.SIGN_AND_SUBMIT_TRANSACTION,
          metadata: this._metadata,
          data: {
            serializedTransaction: {
              data: transaction,
              sender: this.accountAddress,
            },
          },
        },
        (res) => {
          console.log('signAndSubmitTransaction res: ', res);
          if (res.hash) {
            const result: UserResponse<{ hash: string }> = {
              status: UserResponseStatus.APPROVED,
              args: {
                hash: res.hash,
              },
            };
            resolve(result);
          } else {
            const result: UserRejection = { status: UserResponseStatus.REJECTED, message: res?.message };
            resolve(result);
          }
        }
      );
    });
  };

  signTransaction = (
    transactionHex: string,
    transactionType: EndlessWalletTransactionType = EndlessWalletTransactionType.SIMPLE
  ): Promise<UserResponse<{ data: string }>> => {
    return new Promise(async (resolve) => {
      if (!this.accountAddress || !this._endless) {
        const result: UserRejection = { status: UserResponseStatus.REJECTED, message: 'Wallet not linked' };
        resolve(result);
        return;
      }

      this.message?.sendMessage(
        {
          uuid: new Date().getTime().toString(),
          methodName: MethodName.SIGN_BUILD_TRANSACTION,
          metadata: this._metadata,
          data: {
            transactionData: transactionHex,
            sender: this.accountAddress,
            type: transactionType
          },
        },
        (res) => {
          if (res.signature) {
            const result = {

              status: UserResponseStatus.APPROVED,
              args: {
                data: res.signature
              },
            };
            resolve(result);
          } else {
            const result: UserRejection = { status: UserResponseStatus.REJECTED, message: res?.message };
            resolve(result);
          }
        }
      );
    });
  };

  onAccountChange = (callback: (data: AccountInfo) => void) => {
    this.on(EndLessSDKEvent.ACCOUNT_CHANGE, callback);
  };

  onNetworkChange = (callback: (data: NetworkInfo) => void) => {
    this.on(EndLessSDKEvent.NETWORK_CHANGE, callback);
  };
}
