package com.castlabs.reactnative.errors;

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.castlabs.android.player.exceptions.CastlabsPlayerException;
import com.castlabs.reactnative.utils.BundleConverter;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;

/**
 * The PRESTOplay error.
 */
public class PrestoPlayError extends Exception {
  public final @NonNull ErrorCode code;
  public final @NonNull ErrorSeverity severity;
  public final @Nullable String message;
  public final @Nullable String causeMessage;
  public final @Nullable ErrorMetaData metadata;
  public @Nullable CastlabsPlayerException nativeError;

  /**
   * Creates an error for the given severity and code.
   *
   * @param severity the error severity
   * @param code     the error code
   */
  public PrestoPlayError(@NonNull ErrorSeverity severity, @NonNull ErrorCode code) {
    this.code = code;
    this.severity = severity;
    this.message = null;
    this.causeMessage = null;
    this.metadata = null;
    this.nativeError = null;
  }

  /**
   * Creates an error for the given severity, code and message.
   *
   * @param severity the error severity
   * @param code     the error code
   * @param message  the error message
   */
  public PrestoPlayError(
      @NonNull ErrorSeverity severity,
      @NonNull ErrorCode code,
      @Nullable String message
  ) {
    this.code = code;
    this.severity = severity;
    this.message = message;
    this.causeMessage = null;
    this.metadata = null;
    this.nativeError = null;
  }
  
  /**
   * Creates an error for the given severity, code, message, and metadata.
   *
   * @param severity the error severity
   * @param code     the error code
   * @param message  the error message
   * @param metadata the error metadata
   */
  public PrestoPlayError(
      @NonNull ErrorSeverity severity,
      @NonNull ErrorCode code,
      @Nullable String message,
      @Nullable String causeMessage,
      @Nullable ErrorMetaData metadata
  ) {
    this.code = code;
    this.severity = severity;
    this.message = message;
    this.causeMessage = causeMessage;
    this.metadata = metadata;
    this.nativeError = null;
  }

  /**
   * Creates and error for the given Android error.
   *
   * @param castlabsPlayerException the Android error
   */
  public PrestoPlayError(
      @NonNull CastlabsPlayerException castlabsPlayerException
  ) {
    this(
        toErrorSeverity(castlabsPlayerException),
        toErrorCode(castlabsPlayerException),
        castlabsPlayerException.getMessage(),
        castlabsPlayerException.getCauseMessage(),
        new ErrorMetaData(castlabsPlayerException.getErrorData())
    );
    this.nativeError = castlabsPlayerException;
  }

  public WritableMap toJson () {
    WritableMap jsonError = Arguments.createMap();

    jsonError.putInt("severity", severity.value);
    jsonError.putInt("code", code.value);

    jsonError.putString("message", message);
    jsonError.putString("causeMessage", causeMessage);

    if (metadata != null) {
      WritableMap jsonMetadata = Arguments.createMap();

      jsonMetadata.putString("url", metadata.url);
      jsonMetadata.putString("httpResponseBody", metadata.httpResponseBody);
      if (metadata.httpStatusCode != null) {
        jsonMetadata.putInt("httpStatusCode", metadata.httpStatusCode);
      }

      jsonError.putMap("metadata", jsonMetadata);
    }

    if (nativeError != null) {
      WritableMap jsonMetadata = Arguments.createMap();
      jsonMetadata.putInt("severity", nativeError.getSeverity());
      jsonMetadata.putInt("type", nativeError.getType());
      jsonMetadata.putString("message", nativeError.getMessage());
      jsonMetadata.putString("causeMessage", nativeError.getCauseMessage());
      Bundle errorData = nativeError.getErrorData();
      if (errorData != null) {
        jsonMetadata.putMap("metadata", BundleConverter.fromBundle(errorData));
      }
      jsonError.putMap("nativeError", jsonMetadata);
    }
    return jsonError;
  }


  @NonNull
  private static ErrorSeverity toErrorSeverity(
      @NonNull CastlabsPlayerException castlabsPlayerException
  ) {
    switch (castlabsPlayerException.getSeverity()) {
      case CastlabsPlayerException.SEVERITY_ERROR:
        return ErrorSeverity.FATAL;
      default:
        return ErrorSeverity.RECOVERABLE;
    }
  }

  @NonNull
  private static ErrorCode toErrorCode(
      @NonNull CastlabsPlayerException castlabsPlayerException
  ) {
    switch (castlabsPlayerException.getType()) {
      case CastlabsPlayerException.TYPE_DOWNLOAD_ERROR:
        return ErrorCode.MEDIA_DOWNLOADING_FAILED;
      case CastlabsPlayerException.TYPE_DRM_KEY_DOWNLOAD_ERROR:
        return ErrorCode.DRM_KEY_DOWNLOADING_FAILED;
      case CastlabsPlayerException.TYPE_CONNECTIVITY_LOST_ERROR:
        return ErrorCode.CONNECTIVITY_LOST;
      case CastlabsPlayerException.TYPE_CONNECTIVITY_GAINED_INFO:
        return ErrorCode.CONNECTIVITY_GAINED;
      case CastlabsPlayerException.TYPE_DATA_DOWLOAD_ERROR:
        return ErrorCode.DATA_DOWNLOADING_FAILED;
      case CastlabsPlayerException.TYPE_DNS_SERVER_ERROR:
        return ErrorCode.DNS_LOOKUP_FAILED;
      case CastlabsPlayerException.TYPE_TIME_OUT_ERROR:
        return ErrorCode.TIMEOUT_OCCURRED;
      case CastlabsPlayerException.TYPE_TEXT_UNSUPPORTED:
        return ErrorCode.TEXT_UNSUPPORTED;
      case CastlabsPlayerException.TYPE_PLAYBACK_ERROR:
        return ErrorCode.MEDIA_ERROR;
      case CastlabsPlayerException.TYPE_AUDIO_TRACK_INITIALIZATION:
        return ErrorCode.AUDIO_TRACK_INITIALIZATION_FAILED;
      case CastlabsPlayerException.TYPE_AUDIO_DECODER_INITIALIZATION:
        return ErrorCode.AUDIO_DECODER_INITIALIZATION_FAILED;
      case CastlabsPlayerException.TYPE_AUDIO_WRITE_ERROR:
        return ErrorCode.AUDIO_WRITE_ERROR;
      case CastlabsPlayerException.TYPE_VIDEO_DECODER_INITIALIZATION:
        return ErrorCode.VIDEO_DECODER_INITIALIZATION;
      case CastlabsPlayerException.TYPE_AUDIO_UNSUPPORTED:
        return ErrorCode.AUDIO_UNSUPPORTED;
      case CastlabsPlayerException.TYPE_VIDEO_UNSUPPORTED:
        return ErrorCode.VIDEO_UNSUPPORTED;
      case CastlabsPlayerException.TYPE_NO_PLAYABLE_CONTENT:
        return ErrorCode.NO_PLAYABLE_CONTENT;
      case CastlabsPlayerException.TYPE_MANIFEST_LOADING_FAILED:
        return ErrorCode.MANIFEST_LOADING_FAILED;
      case CastlabsPlayerException.TYPE_MANIFEST_PARSING_FAILED:
        return ErrorCode.MANIFEST_PARSING_FAILED;
      case CastlabsPlayerException.TYPE_MANIFEST_INVALID:
        return ErrorCode.MANIFEST_INVALID;
      case CastlabsPlayerException.TYPE_BEHIND_LIVE_WINDOW:
        return ErrorCode.BEHIND_LIVE_WINDOW;
      case CastlabsPlayerException.TYPE_DRM_PROVISION_ERROR:
        return ErrorCode.DRM_PROVISIONING_FAILED;
      case CastlabsPlayerException.TYPE_DRMTODAY_EXCEPTION:
        return ErrorCode.DRMTODAY_ERROR;
      case CastlabsPlayerException.TYPE_AUDIO_DECRYPTION_ERROR:
        return ErrorCode.AUDIO_DECRYPTION_FAILED;
      case CastlabsPlayerException.TYPE_VIDEO_DECRYPTION_ERROR:
        return ErrorCode.VIDEO_DECRYPTION_FAILED;
      case CastlabsPlayerException.TYPE_SECONDARY_DISPLAY:
        return ErrorCode.SECONDARY_DISPLAY_FORBIDDEN;
      case CastlabsPlayerException.TYPE_KEY_EXPIRED:
        return ErrorCode.KEY_EXPIRED;
      case CastlabsPlayerException.TYPE_HDCP_CONNECTION_WARNING:
        return ErrorCode.HDCP_CONNECTION_WARNING;
      case CastlabsPlayerException.TYPE_CSL_LIMIT_REACHED:
        return ErrorCode.CSL_LIMIT_REACHED;
      case CastlabsPlayerException.TYPE_CSL_NETWORK_ERROR:
        return ErrorCode.CSL_NETWORK_ERROR;
      case CastlabsPlayerException.TYPE_UNKNOWN:
        return ErrorCode.PLAYER_ERROR;
      case CastlabsPlayerException.TYPE_NO_RENDERER_FOUND:
        return ErrorCode.NO_RENDERER_FOUND;
      case CastlabsPlayerException.TYPE_USER_ID_NOT_PROVIDED:
        return ErrorCode.USER_ID_NOT_PROVIDED;
      case CastlabsPlayerException.TYPE_AD_ERROR:
        return ErrorCode.AD_ERROR;
      case CastlabsPlayerException.TYPE_AD_REQUEST_UNSUPPORTED:
        return ErrorCode.AD_REQUEST_UNSUPPORTED;
      case CastlabsPlayerException.TYPE_SDK_INIT_ERROR:
        return ErrorCode.SDK_INITIALIZATION_FAILED;
      case CastlabsPlayerException.TYPE_SDK_NOT_INITIALIZED:
        return ErrorCode.SDK_NOT_INITIALIZED;
      case CastlabsPlayerException.TYPE_INVALID_PLAYER_LICENSE:
        return ErrorCode.SDK_INVALID_LICENCE;
      case CastlabsPlayerException.TYPE_API_MISMATCH:
        return ErrorCode.PLATFORM_API_MISMATCH;
      default:
        return ErrorCode.PLAYER_ERROR;
    }
  }
}
