import { AccountInfo, EndlessLuffaSdk, MethodName } from '../index';
import type { IMessageData, EndLessSDKEventListenersType, EndLessSDKEventPayload, EndLessSDKEventType } from './types';
import { EndLessSDKEvent, IResponseMessageData } from './types';
import { getNetworkInfo, isLuffaMiniProgramWebview, isLuffaMiniProgram, isLuffa, networkMap } from '../utils';

export class PostMessage {
  private static _instance: PostMessage;
  callbacks: {
    [key: string]: (data: unknown) => void;
  } = {};
  private listeners: EndLessSDKEventListenersType = {};

  constructor() {
    if (PostMessage._instance) return PostMessage._instance;
    PostMessage._instance = this;
    if (isLuffa()) {
      window.endlessWallet = {
        sendResponse: this.sendResponse.bind(this),
      };
    }
  }
  // wallet emit sdk
  // SDK processing method itself
  private readonly receive = (msg: { data: IResponseMessageData }) => {
    const eventType = msg.data.methodName as EndLessSDKEventType | MethodName;
    // sdk callbacks
    if (this.callbacks[msg.data.uuid + eventType]) {
      this.callbacks[msg.data.uuid + eventType](msg.data.data);
      delete this.callbacks[msg.data.uuid + eventType];
    }

    // Some events are being monitored by dapp and require running a monitoring callback
    switch (eventType) {
      case EndLessSDKEvent.NETWORK_CHANGE: {
        let network = getNetworkInfo(msg.data.data.network);
        if (isLuffaMiniProgram()) {
          network = msg.data.data;
        }
        this.emit(eventType, network);
        break;
      }

      // wallet to sdk send onAccountChange | connect
      // sdk to dapp send event
      // CONNECT and ACCOUNT_CHANGE return account as AccountAddress
      case EndLessSDKEvent.CONNECT:
      case EndLessSDKEvent.ACCOUNT_CHANGE: {
        const accountInfo: AccountInfo = {
          ...msg.data.data,
        };
        if (msg?.data?.data?.account) {
          EndlessLuffaSdk.setAccountAddress(msg.data.data.account);
        } else {
          EndlessLuffaSdk.setAccountAddress(null);
        }
        this.emit(eventType, accountInfo);
        break;
      }

      case EndLessSDKEvent.DISCONNECT: {
        EndlessLuffaSdk.setAccountAddress(null);
        this.emit(eventType, msg.data.data);
        break;
      }

      default:
        this.emit(eventType as EndLessSDKEventType, msg.data.data);
        break;
    }
  };

  readonly addListener = <K extends EndLessSDKEventType>(
    methodName: K,
    callback: (payload: EndLessSDKEventPayload<K>) => void
  ) => {
    if (!this.listeners[methodName]) {
      this.listeners[methodName] = [];
    }
    this.listeners[methodName].push(callback);
  };

  readonly removeListener = <K extends EndLessSDKEventType>(
    methodName: K,
    callback?: (payload: EndLessSDKEventPayload<K>) => void
  ) => {
    if (callback) {
      const index = this.listeners[methodName]?.indexOf(callback) ?? -1;
      if (index > -1) {
        this.listeners?.[methodName]?.splice(index, 1);
      }
    } else {
      this.listeners[methodName] = [];
    }
  };

  readonly emit = <K extends EndLessSDKEventType>(methodName: K, payload: EndLessSDKEventPayload<K>) => {
    this.listeners?.[methodName]?.forEach((d) => d(payload));
  };

  readonly sendMessage = (data: IMessageData, callback?: (data: any) => void) => {

    try {
      if (isLuffa()) {
        this.sendLuffaMessage(data, callback);
        return;
      }
      if (isLuffaMiniProgram() || isLuffaMiniProgramWebview()) {
        this.sendMiniProgramMessage(data, callback);
        return;
      }
    } catch (error) {
      console.error('sendMessage error: ', error);
    }
  };

  private sendLuffaMessage(data: IMessageData, callback?: (data: any) => void) {
    const initData =  data?.initData || EndlessLuffaSdk.getIninData();
    data.initData = {
      ...(initData || {}),
      network: networkMap[initData.network] ?? initData.network
    },
    data.from = EndlessLuffaSdk.getAccountAddress();
    if (callback) {
      this.callbacks[data.uuid + data.methodName] = callback;
    }
    if (window?._endlessWallet) {
      window._endlessWallet?.sendMessage(JSON.stringify(data));
    } else if (window?.webkit && window?.webkit?.messageHandlers?._endlessWallet) {
      window.webkit.messageHandlers._endlessWallet?.postMessage(data);
    }
  }

  private sendResponse(response: string) {
    const responseData = JSON.parse(response);
    console.log('responseData: ', responseData);
    this.receive({
      data: responseData,
    });
  }

  private sendMiniProgramMessage(data: IMessageData, callback?: (data: any) => void) {
    let funName = 'invokeNativePlugin';
    const accountAddress = EndlessLuffaSdk.getAccountAddress();
    const initData = EndlessLuffaSdk.getIninData();
    let api_name = 'luffaWebRequest';
    let params = {
      api_name,
      data: {
        func: data.methodName,
        chainType: 'endless',
        ...data,
        initData: {
          ...initData,
          network: networkMap[initData.network] ?? initData.network
        },
        from: accountAddress,
      },
    };
    let callbackSuccessFun = (res: any) => {};
    let callbackErrorFun = (res: any) => {};
    switch (data.methodName) {
      case MethodName.NETWORK_CHANGE:
        this.emit(data.methodName as unknown as EndLessSDKEventType, data.data as any);
        return;
      case MethodName.DISCONNECT:
        EndlessLuffaSdk.setAccountAddress(null);
        this.emit(data.methodName as unknown as EndLessSDKEventType, undefined);
      case MethodName.CONNECT:
      case MethodName.GETACCOUNT:
        callbackSuccessFun = (res) => {
          EndlessLuffaSdk.setAccountAddress(res.data.account);
          callback &&
            callback({
              account: res?.data?.address || '',
              ...(res.data || {}),
            });
        };
        callbackErrorFun = (res) => {
          callback &&
            callback({
              account: '',
            });
        };
        break;
      case MethodName.SIGN_MESSAGE:
      case MethodName.SIGN_BUILD_TRANSACTION:
      case MethodName.SIGN_AND_SUBMIT_TRANSACTION:
        callbackSuccessFun = (res: any) => {
          callback &&
            callback({
              status: 'success',
              ...(res.data || {}),
            });
        };
        callbackErrorFun = (res: any) => {
          callback &&
            callback({
              status: 'error',
              ...(res.data || {}),
            });
        };
        break;

      default:
        callbackSuccessFun = (res: any) => {
          callback &&
            callback({
              status: 'success',
              ...(res.data || {}),
            });
        };
        callbackErrorFun = (res: any) => {
          callback &&
            callback({
              status: 'error',
              ...(res.data || {}),
            });
        };
        break;
    }
    console.log('funName: ', funName);
    console.log('params: ', params);
    if (isLuffaMiniProgram()) {
      window.wx[funName]({
        ...params,
        complete: (res: any) => {
          console.log(`wx ${funName} res: `, res);
          if (res.status === 'success') {
            callbackSuccessFun(res);
            this.receive({
              data: res,
            });
          } else {
            callbackErrorFun(res);
          }
        }
      });
    } else if (isLuffaMiniProgramWebview()) {
      window.WeixinJSBridge?.invoke(funName, params, (res) => {
        console.log(`WeixinJSBridge ${funName} res: `, res);
        if (res.status === 'success') {
          callbackSuccessFun(res);
          this.receive({
            data: res,
          });
        } else {
          callbackErrorFun(res);
        }
      });
    }
  }
}
