import React from 'react';
import {
  EmitterSubscription,
  NativeEventEmitter,
  NativeModules,
  requireNativeComponent,
  StyleSheet,
  View,
} from 'react-native';
import { MFCurrencyISO, MFV3PaymentMode } from './MFEnums';
import {
  MFV3GetSessionData,
  MFV3PaymentResult,
  MFV3SessionData,
  MFV3VerifyResult,
  MFV3WidgetEvent,
  MFCardViewStyle,
  MFError,
} from './MFModels';
import { modelParser } from './MFUtils';

//#region V3 Card View (MFCardV3Module)

// Fall back to a no-op so `new NativeEventEmitter(...)` never throws at import even
// if the native module isn't linked. Method calls will surface a clear error.
const MFCardV3Native = NativeModules.MFCardV3Module ?? { addListener: () => {}, removeListeners: () => {} };
const MFCardV3Emitter = new NativeEventEmitter(MFCardV3Native);

const CardViewV3 = requireNativeComponent<IMFCardV3NativeProps>('MFCardViewV3');

const MFCardV3Constants = {
  CardResizedEventName: 'onMFV3CardResized',
  WidgetEventName: 'onMFV3WidgetEvent',
  ErrorEventName: 'onMFV3Error',
};

const styles = StyleSheet.create({
  container: { width: '100%' },
});

interface IMFCardV3NativeProps {
  style?: any;
  paymentStyle?: MFCardViewStyle;
  cardId?: string;
}

// Unique id per mounted MFCardViewV3 instance. The native view registers itself
// under this id so the module's promise methods can reach the exact instance —
// works identically across old/new RN architecture and (in future) Android,
// without relying on findNodeHandle / UIManager view resolution.
let _nextCardId = 0;
const genCardId = (): string => `mfcardv3-${++_nextCardId}`;

export interface IMFCardViewV3Props {
  /** Width/positioning styles for the card view container. Height is driven automatically. */
  style?: any;
  /** Reused MFCardViewStyle object. `ShowCardholderName` is V3-only. */
  paymentStyle?: MFCardViewStyle;
  /** Fired whenever the widget's content height changes (the view resizes itself too). */
  onHeightChanged?: (height: number) => void;
  /** Fired for subscribed widget lifecycle events (VIEW_READY, CARD_IDENTIFIED, ...). */
  onWidgetEvent?: (event: MFV3WidgetEvent) => void;
  /** Fired if the widget fails to load (e.g. a session id missing its country prefix). */
  onError?: (error: MFError) => void;
}

interface IMFCardViewV3State {
  height: number;
}

/**
 * The V3 embedded card view. Renders a native, per-instance card widget (no singleton).
 * Usage:
 *   const ref = useRef<MFCardViewV3>(null);
 *   const session = await MFV3Session.create({ amount: 1, currency: MFCurrencyISO.KUWAIT_KWD });
 *   await ref.current.load(session);
 *   const result = await ref.current.submit(MFCurrencyISO.KUWAIT_KWD);
 */
export class MFCardViewV3 extends React.Component<IMFCardViewV3Props, IMFCardViewV3State> {
  private cardId = genCardId();
  private subscriptions: EmitterSubscription[] = [];

  constructor(props: IMFCardViewV3Props) {
    super(props);
    this.state = { height: 0 };
  }

  componentDidMount() {
    this.subscriptions.push(
      MFCardV3Emitter.addListener(MFCardV3Constants.CardResizedEventName, (payload: { cardId: string; height: number }) => {
        if (!this.isForThisView(payload?.cardId)) return;
        const height = payload.height;
        if (height > 1) this.setState({ height });
        this.props.onHeightChanged && this.props.onHeightChanged(height);
      })
    );

    this.subscriptions.push(
      MFCardV3Emitter.addListener(MFCardV3Constants.WidgetEventName, (payload: { cardId: string } & MFV3WidgetEvent) => {
        if (!this.isForThisView(payload?.cardId)) return;
        this.props.onWidgetEvent &&
          this.props.onWidgetEvent({ name: payload.name, id: payload.id, paymentMethodName: payload.paymentMethodName });
      })
    );

    this.subscriptions.push(
      MFCardV3Emitter.addListener(MFCardV3Constants.ErrorEventName, (payload: { cardId: string } & MFError) => {
        if (!this.isForThisView(payload?.cardId)) return;
        this.props.onError && this.props.onError({ code: payload.code, message: payload.message } as MFError);
      })
    );
  }

  componentWillUnmount() {
    this.subscriptions.forEach((s) => s.remove());
    this.subscriptions = [];
  }

  private isForThisView(cardId?: string): boolean {
    return cardId != null && cardId === this.cardId;
  }

  /** Renders the card widget for the given session. Call before submit/verify. */
  async load(session: MFV3SessionData): Promise<void> {
    await MFCardV3Native.load(this.cardId, session);
  }

  /** Submits the entered card (pay / collect, per the session mode). */
  async submit(currency?: MFCurrencyISO): Promise<MFV3PaymentResult> {
    const jsonResponse = await MFCardV3Native.submit(this.cardId, currency ?? '');
    return modelParser<MFV3PaymentResult>(jsonResponse);
  }

  /** Verifies the entered card (VERIFY session — no charge). */
  async verify(): Promise<MFV3VerifyResult> {
    const jsonResponse = await MFCardV3Native.verify(this.cardId);
    return modelParser<MFV3VerifyResult>(jsonResponse);
  }

  render() {
    const { style, paymentStyle } = this.props;
    return (
      <View style={[styles.container, style, this.state.height > 0 ? { height: this.state.height } : null]}>
        <CardViewV3 style={StyleSheet.absoluteFill} paymentStyle={paymentStyle} cardId={this.cardId} />
      </View>
    );
  }
}

//#endregion

//#region Session API

export interface IMFV3CreateSessionRequest {
  amount: number;
  currency?: MFCurrencyISO;
  mode?: MFV3PaymentMode;
  require3DS?: boolean;
  saveCard?: boolean;
  customerReference?: string;
}

export interface IMFV3CreateVerifySessionRequest {
  customerReference: string;
  currency?: MFCurrencyISO;
  require3DS?: boolean;
}

/**
 * Session helpers. Two integration paths:
 * - SDK path: create / createVerify use the API key set via MFSDK.init.
 * - Backend path: your server creates the session; pass the id to fromBackend.
 */
export const MFV3Session = {
  async create(request: IMFV3CreateSessionRequest): Promise<MFV3SessionData> {
    const jsonResponse = await MFCardV3Native.create({
      Amount: request.amount,
      Currency: request.currency ?? null,
      Mode: request.mode ?? MFV3PaymentMode.COMPLETE_PAYMENT,
      Require3DS: request.require3DS ?? true,
      SaveCard: request.saveCard ?? false,
      CustomerReference: request.customerReference ?? null,
    });
    return modelParser<MFV3SessionData>(jsonResponse);
  },

  async createVerify(request: IMFV3CreateVerifySessionRequest): Promise<MFV3SessionData> {
    const jsonResponse = await MFCardV3Native.createVerify({
      CustomerReference: request.customerReference,
      Currency: request.currency ?? null,
      Require3DS: request.require3DS ?? true,
    });
    return modelParser<MFV3SessionData>(jsonResponse);
  },

  /**
   * Backend path: build a session from an id your server created via POST /v3/sessions.
   * SessionId must be the full country-prefixed id (e.g. "KWT-abc123"). Pass
   * encryptionKey too if you want the SDK to decrypt the inline (non-3DS) result.
   */
  fromBackend(sessionId: string, encryptionKey?: string): MFV3SessionData {
    return { SessionId: sessionId, EncryptionKey: encryptionKey };
  },
};

//#endregion

// Re-export the request types so the whole V3 API can be imported from one place.
export type { MFV3PaymentResult, MFV3VerifyResult, MFV3SessionData, MFV3GetSessionData, MFV3WidgetEvent };
