import { Platform } from 'react-native';

import type {
  AuthenticateParams,
  CallOptions,
  DirectCallLogQueryParams,
  DirectCallProperties,
  RoomListQueryParams,
  RoomParams,
  SendbirdCallListener,
  SendbirdCallsJavascriptSpec,
  User,
} from '../types';
import { NativeConstants, NativeQueryType, RoomState, RoomType, SoundType } from '../types';
import { Logger } from '../utils/logger';
import Version from '../version';
import { DirectCallLogListQuery, RoomListQuery } from './BridgedQuery';
import { DirectCall } from './DirectCall';
import NativeBinder, { CallsEvent, DefaultEventType } from './NativeBinder';
import { Room } from './Room';

/**
 * SendbirdCallsModule class for SendbirdCalls
 */
export default class SendbirdCallsModule implements SendbirdCallsJavascriptSpec {
  constructor(private binder: NativeBinder) {}

  private _applicationId = '';
  private _initialized = false;
  private _currentUser: User | null = null;
  private _sendbirdCallListener: SendbirdCallListener | null = null;

  /**
   * Returns current React-Native SDK version.
   *
   * @since 1.0.0
   */
  public get VERSION() {
    return Version;
  }

  /**
   * Returns current iOS/Android SDK version.
   *
   * @since 1.0.0
   */
  public get NATIVE_VERSION() {
    return this.getConstants()['NATIVE_SDK_VERSION'];
  }

  /**
   * Returns the SDK Logger
   *
   * @since 1.0.0
   */
  public get Logger() {
    return {
      setLogLevel: (level: 'none' | 'error' | 'warning' | 'info') => {
        this.setLoggerLevel(level);
      },
      getLogLevel: Logger.getLogLevel,
      info: Logger.info,
      warn: Logger.warn,
      error: Logger.error,
    };
  }

  /**
   * Returns current application ID.
   *
   * @since 1.0.0
   */
  public get applicationId() {
    return this._applicationId;
  }

  /**
   * Returns is SDK initialized.
   *
   * @since 1.0.0
   */
  public get initialized() {
    return this._initialized;
  }

  /**
   * Gets the current `User`.
   * Returns the current `User`. If SendbirdCalls is not authenticated, `null` will be returned.
   *
   * @since 1.0.0
   */
  public get currentUser() {
    return this._currentUser;
  }

  /**
   * An enum that represents different types of a room.
   * Returns {@link RoomType}
   *
   * @since 1.0.0
   */
  public get RoomType() {
    return RoomType;
  }

  /**
   * An enum that represents state of a room.
   * Returns {@link RoomState}
   *
   * @since 1.0.0
   */
  public get RoomState() {
    return RoomState;
  }

  /**
   * Set logger level
   *
   * @since 1.1.0
   * */
  setLoggerLevel = (level: 'none' | 'error' | 'warning' | 'info') => {
    Logger.setLogLevel(level);
    this.binder.nativeModule.setLoggerLevel(level);
  };

  /**
   * Gets the constants from React-Native Native module
   * Returns the object
   *
   * @since 1.0.0
   */
  protected getConstants = (): NativeConstants => {
    // @ts-ignore
    return this.binder.nativeModule.getConstants?.() ?? { NATIVE_SDK_VERSION: '' };
  };

  /**
   * Adds sound used in DirectCall such as ringtone and some sound effects with its file name with extension
   *
   * @iOS bundle file name
   * @Android res/raw file name
   *
   * @since 1.0.0
   */
  public addDirectCallSound = (type: SoundType, fileName: string) => {
    let name = fileName;
    if (Platform.OS === 'android') {
      const idx = fileName.lastIndexOf('.');
      if (idx) name = fileName.slice(0, idx);
    }
    this.binder.nativeModule.addDirectCallSound(type, name);
  };

  /**
   * Removes sound used in {@link DirectCall} with {@link SoundType} value.
   *
   * @since 1.0.0
   */
  public removeDirectCallSound = (type: SoundType) => {
    this.binder.nativeModule.removeDirectCallSound(type);
  };

  /**
   * Enables / disables dial sound used in {@link DirectCall} even when the device is in silent mode.
   * Call this method right after {@link addDirectCallSound}.
   *
   * @since 1.0.0
   */
  public setDirectCallDialingSoundOnWhenSilentOrVibrateMode = (enabled: boolean) => {
    this.binder.nativeModule.setDirectCallDialingSoundOnWhenSilentOrVibrateMode(enabled);
  };

  /**
   * Gets the current `User` from native
   * Returns the current `User`. If SendbirdCalls is not authenticated, `null` will be returned.
   *
   * @since 1.0.0
   */
  public getCurrentUser = async () => {
    this._currentUser = await this.binder.nativeModule.getCurrentUser();
    return this.currentUser;
  };

  /**
   * Returns all ongoing calls, including the active call and all calls on hold.
   *
   * @since 1.0.0
   */
  public getOngoingCalls(): Promise<DirectCallProperties[]> {
    return this.binder.nativeModule.getOngoingCalls();
  }

  /**
   * Gets call from call ID or call UUID
   *
   * @since 1.0.0
   */
  public getDirectCall = async (callId: string): Promise<DirectCall> => {
    const callProps = await this.binder.nativeModule.getDirectCall(callId);
    return DirectCall.get(this.binder, callProps);
  };

  /**
   * Initializes SendbirdCalls.
   *
   * @since 1.0.0
   */
  public initialize = (appId: string) => {
    if (this.initialized) {
      if (this.applicationId !== appId) {
        return this._init(appId);
      } else {
        return this.initialized;
      }
    } else {
      return this._init(appId);
    }
  };

  private _init = (appId: string) => {
    this.Logger.info('[SendbirdCalls]', 'initialize()');

    DirectCall.poolRelease();
    Room.poolRelease();

    if (!this.initialized) {
      this.binder.addListener(CallsEvent.DEFAULT, ({ type, data }) => {
        if (type === DefaultEventType.ON_RINGING) {
          this.Logger.info('[SendbirdCalls]', 'onRinging', data.callId);
          this._sendbirdCallListener?.onRinging(data);
        }
      });
    }

    this.binder.nativeModule.initialize(appId);
    this._applicationId = appId;
    this._initialized = true;
    return this.initialized;
  };

  /**
   * Authenticates.
   *
   * @since 1.0.0
   */
  public authenticate = async (authParams: AuthenticateParams) => {
    this._currentUser = await this.binder.nativeModule.authenticate(authParams);
    return this.currentUser as User;
  };

  /**
   * Deauthenticates.
   *
   * @since 1.0.0
   */
  public deauthenticate = async () => {
    await this.binder.nativeModule.deauthenticate();
    this._currentUser = null;
  };

  /**
   * Registers push token for current user.
   *
   * on iOS, push token means APNS token.
   * on Android, push token means FCM token.
   *
   * ```ts
   * if (Platform.OS === 'android') {
   *   const fcmToken = await messaging().getToken();
   *   await SendbirdCalls.registerPushToken(fcmToken);
   * }
   * if (Platform.OS === 'ios') {
   *   const apnsToken = await messaging().getAPNSToken();
   *   await SendbirdCalls.registerPushToken(apnsToken);
   * }
   * ```
   *
   * @since 1.0.0
   */
  public registerPushToken = async (token: string, unique = true) => {
    await this.binder.nativeModule.registerPushToken(token, unique);
  };

  /**
   * Unregisters push token for current user.
   *
   * @since 1.0.0
   */
  public unregisterPushToken = async (token: string) => {
    await this.binder.nativeModule.unregisterPushToken(token);
  };

  /**
   * Makes a call to user(callee) directly. (1:1 Call).
   * Use the {@link CallOptions} object to choose initial call configuration (e.g. muted/unmuted)
   *
   * @since 1.0.0
   */
  public dial(
    calleeUserId: string,
    isVideoCall: boolean,
    options: CallOptions = { audioEnabled: true, frontCamera: true, videoEnabled: true },
  ): Promise<DirectCallProperties> {
    return this.binder.nativeModule.dial(calleeUserId, isVideoCall, options);
  }

  /**
   * Creates a {@link Room} for group calls.
   *
   * @since 1.0.0
   */
  public createRoom(roomParams: RoomParams): Promise<Room> {
    return this.binder.nativeModule.createRoom(roomParams).then((props) => Room.get(this.binder, props));
  }

  /**
   * Fetches a room instance from Sendbird server.
   *
   * @since 1.0.0
   */
  public fetchRoomById(roomId: string): Promise<Room> {
    return this.binder.nativeModule.fetchRoomById(roomId).then((props) => Room.get(this.binder, props));
  }

  /**
   * Gets a locally-cached room instance by room ID.
   *
   * @since 1.0.0
   */
  public getCachedRoomById(roomId: string): Promise<Room | null> {
    return this.binder.nativeModule
      .getCachedRoomById(roomId)
      .then((props) => (props ? Room.get(this.binder, props) : null));
  }

  /**
   * To receive native-like calls while an app is in the background or closed, a device registration token must be registered to the server.
   * Register a device push token after authentication has completed using the `SendbirdCalls.ios_registerVoIPPushToken()` method.
   *
   * @platform iOS
   * @since 1.0.0
   */
  public ios_registerVoIPPushToken = async (token: string, unique = true) => {
    if (Platform.OS !== 'ios') return;
    await this.binder.nativeModule.registerVoIPPushToken(token, unique);
  };

  /**
   * Unregisters a VoIP push token of specific device.
   * You will not receive VoIP push notification for a call anymore.
   *
   * @platform iOS
   * @since 1.0.0
   */
  public ios_unregisterVoIPPushToken = async (token: string) => {
    if (Platform.OS !== 'ios') return;
    await this.binder.nativeModule.unregisterVoIPPushToken(token);
  };

  /**
   * Show-up a view that allows user to change the system audio route.
   * [AVRoutePickerView](https://developer.apple.com/documentation/avkit/avroutepickerview) in iOS 11 or later
   *
   * @platform iOS
   * @since 1.0.0
   */
  public ios_routePickerView = () => {
    if (Platform.OS !== 'ios') return;
    this.binder.nativeModule.routePickerView();
  };

  /**
   * Handles Firebase message data.
   * Returns true if Sendbird call message. Otherwise false.
   *
   * @platform Android
   * @since 1.0.0
   */
  public android_handleFirebaseMessageData = (data?: { [key: string]: string | object }) => {
    if (Platform.OS !== 'android' || !data?.['sendbird_call']) {
      return false;
    } else {
      //@ts-ignore
      this.binder.nativeModule.handleFirebaseMessageData(data);
      return true;
    }
  };

  /**
   * Set SendbirdCall listener
   *
   * @since 1.0.0
   */
  setListener(listener: SendbirdCallListener): void {
    this.Logger.info('[SendbirdCalls]', 'setListener');
    this._sendbirdCallListener = listener;
  }

  /**
   * Creates direct call log list query.
   *
   * @since 1.0.0
   */
  public createDirectCallLogListQuery = async (params: DirectCallLogQueryParams = {}) => {
    const queryKey = await this.binder.nativeModule.createDirectCallLogListQuery(params);
    return new DirectCallLogListQuery(queryKey, NativeQueryType.DIRECT_CALL_LOG, this.binder);
  };

  /**
   * Creates a query for room list with specified parameters.
   *
   * @since 1.0.0
   */
  public createRoomListQuery = async (params: RoomListQueryParams = {}) => {
    const queryKey = await this.binder.nativeModule.createRoomListQuery(params);
    return new RoomListQuery(queryKey, NativeQueryType.ROOM_LIST, this.binder);
  };

  /**
   * Updates custom items for a given call ID.
   *
   * @since 1.1.9
   */
  public updateCustomItems = async (callId: string, customItems: Record<string, string>) => {
    const result = await this.binder.nativeModule.updateCustomItems(callId, customItems);
    if (result && result.updatedItems) {
      DirectCall.updateCustomItems(callId, result.updatedItems);
    }
    return result;
  };

  /**
   * Deletes custom items for a given call ID.
   *
   * @since 1.1.9
   */
  public deleteCustomItems = async (callId: string, customItemKeys: string[]) => {
    const result = await this.binder.nativeModule.deleteCustomItems(callId, customItemKeys);
    if (result && result.updatedItems) {
      DirectCall.updateCustomItems(callId, result.updatedItems);
    }
    return result;
  };

  /**
   * Deletes all custom items for a given call ID.
   *
   * @since 1.1.9
   */
  public deleteAllCustomItems = async (callId: string) => {
    const result = await this.binder.nativeModule.deleteAllCustomItems(callId);
    if (result && result.updatedItems) {
      DirectCall.updateCustomItems(callId, result.updatedItems);
    }
    return result;
  };
}
