import { DeviceEventEmitter, NativeModules, Platform } from 'react-native';
import type {
  AdStateListener,
  BannerAdStateListener,
  ClosableAdStateListener,
  InterstitialAdStateListener,
  NativeAdStateListener,
  RewardedAdStateListener,
} from './AdStateListener';
import { BannerPosition } from './banner/BannerPosition';
import type { ReactNativeTapsell } from '../ReactNativeTapsell';
import type {
  CloseEvent,
  ImpressionEvent,
  ShowFailureEvent,
} from '../messaging/Event';
import { ImpressionType } from './ImpressionType';
import { CompletionState } from '@react-native-tapsell-mediation/tapsell';
import { _mediationEventListenerInstance } from '../messaging/MediationEventListener';
import { nativeAdViewHolderInstance } from './native/NativeAdViewHolder';
import { NativeAdDispatch } from './native/NativeAdDispatch';

const LINKING_ERROR =
  `The package '@react-native-tapsell-mediation/tapsell' doesn't seem to be linked. Make sure: \n\n` +
  Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
  '- You rebuilt the app after installing the package\n' +
  '- You are not using Expo Go\n';

const TapsellMediatorModule = NativeModules.RNTapsellMediation
  ? NativeModules.RNTapsellMediation
  : new Proxy(
      {},
      {
        get() {
          throw new Error(LINKING_ERROR);
        },
      }
    );

/** @internal */
class ShowCourier implements ReactNativeTapsell.ShowCourier {
  constructor() {
    this.subscribeImpressionEvents();
  }

  private readonly _listeners = new Map<string, AdStateListener>();

  private static _instance: ShowCourier;

  public static getInstance(): ShowCourier {
    if (!ShowCourier._instance) {
      ShowCourier._instance = new ShowCourier();
    }
    return ShowCourier._instance;
  }

  showRewardedAd(adId: string, listener: RewardedAdStateListener) {
    this._listeners.set(adId, listener);
    return TapsellMediatorModule.showRewardedAd(adId);
  }

  showInterstitialAd(adId: string, listener: InterstitialAdStateListener) {
    this._listeners.set(adId, listener);
    return TapsellMediatorModule.showInterstitialAd(adId);
  }

  showBannerAd(
    adId: string,
    bannerPosition: BannerPosition,
    listener: BannerAdStateListener
  ) {
    this._listeners.set(adId, listener);
    return TapsellMediatorModule.showBannerAd(adId, bannerPosition);
  }

  showNativeAd(
    adId: string,
    adDispatch: NativeAdDispatch,
    listener: NativeAdStateListener
  ) {
    nativeAdViewHolderInstance.registerAdView(adId, adDispatch);
    this._listeners.set(adId, listener);
    return TapsellMediatorModule.showNativeAd(adId);
  }

  clickNativeAd(adId: string) {
    DeviceEventEmitter.emit('TapsellNativeAdClick', adId);
  }

  destroyBannerAd(adId: string) {
    this._listeners.delete(adId);
    TapsellMediatorModule.destroyBannerAd(adId);
  }

  destroyNativeAd(adId: string) {
    this._listeners.delete(adId);
    TapsellMediatorModule.destroyNativeAd(adId);
  }

  private subscribeImpressionEvents() {
    _mediationEventListenerInstance.registerAdImpressionListener((event) => {
      this.onImpressionEvent(event, ImpressionType.IMPRESSION);
    });
    _mediationEventListenerInstance.registerAdClickListener((event) => {
      this.onImpressionEvent(event, ImpressionType.CLICK);
    });
    _mediationEventListenerInstance.registerAdRewardListener((event) => {
      this.onImpressionEvent(event, ImpressionType.REWARD);
    });
    _mediationEventListenerInstance.registerAdCloseListener((event) => {
      this.onImpressionEvent(event, ImpressionType.CLOSE);
    });
    _mediationEventListenerInstance.registerAdShowFailureListener((event) => {
      this.onImpressionEvent(event, ImpressionType.FAILURE);
    });
  }

  private onImpressionEvent(
    event: ImpressionEvent,
    impressionType: ImpressionType
  ) {
    const listener = this._listeners.get(event.adId);
    if (!listener) {
      throw new Error(`Ad not found for id: ${event.adId}`);
    }
    switch (impressionType) {
      case ImpressionType.IMPRESSION:
        listener.onAdImpression();
        break;
      case ImpressionType.CLICK:
        listener.onAdClicked();
        break;
      case ImpressionType.REWARD:
        (listener as RewardedAdStateListener).onRewarded();
        break;
      case ImpressionType.CLOSE:
        const completionState = CompletionState.fromInt(
          (event as CloseEvent).completionState
        );
        if (completionState !== undefined) {
          (listener as ClosableAdStateListener).onAdClosed(completionState);
        }
        break;
      case ImpressionType.FAILURE:
        listener.onAdFailed((event as ShowFailureEvent).message);
        break;
    }
  }
}

/** @internal */
export const ShowCourierInstance = ShowCourier.getInstance();
