import { NativeModules, Platform } from 'react-native';
import { SDK_VERSION } from './version';

import Response from '@pixelpay/sdk-core/lib/base/Response';
import type Settings from '@pixelpay/sdk-core/lib/models/Settings';
import type AuthTransaction from '@pixelpay/sdk-core/lib/requests/AuthTransaction';
import type CaptureTransaction from '@pixelpay/sdk-core/lib/requests/CaptureTransaction';
import type CardTokenization from '@pixelpay/sdk-core/lib/requests/CardTokenization';
import type SaleTransaction from '@pixelpay/sdk-core/lib/requests/SaleTransaction';
import type StatusTransaction from '@pixelpay/sdk-core/lib/requests/StatusTransaction';
import type VoidTransaction from '@pixelpay/sdk-core/lib/requests/VoidTransaction';
import ErrorResponse from '@pixelpay/sdk-core/lib/responses/ErrorResponse';
import FailureResponse from '@pixelpay/sdk-core/lib/responses/FailureResponse';
import InputErrorResponse from '@pixelpay/sdk-core/lib/responses/InputErrorResponse';
import NetworkFailureResponse from '@pixelpay/sdk-core/lib/responses/NetworkFailureResponse';
import NoAccessResponse from '@pixelpay/sdk-core/lib/responses/NoAccessResponse';
import NotFoundResponse from '@pixelpay/sdk-core/lib/responses/NotFoundResponse';
import PayloadResponse from '@pixelpay/sdk-core/lib/responses/PayloadResponse';
import PaymentDeclinedResponse from '@pixelpay/sdk-core/lib/responses/PaymentDeclinedResponse';
import PreconditionalResponse from '@pixelpay/sdk-core/lib/responses/PreconditionalResponse';
import SuccessResponse from '@pixelpay/sdk-core/lib/responses/SuccessResponse';
import TimeoutResponse from '@pixelpay/sdk-core/lib/responses/TimeoutResponse';
import BaseTransaction from '@pixelpay/sdk-core/lib/services/Transaction';

const LINKING_ERROR =
  `The package '@pixelpay/react-native-plugin' 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 ReactNativePlugin = NativeModules.ReactNativePlugin
  ? NativeModules.ReactNativePlugin
  : new Proxy(
      {},
      {
        get() {
          throw new Error(LINKING_ERROR);
        },
      }
    );

class ServiceBehaviour {
  _settings: Settings;

  constructor(settings: Settings) {
    this._settings = settings;
    this._settings.sdk = 'sdk-react-native';
    this._settings.sdk_version = SDK_VERSION;
  }

  getSettings(): string {
    return JSON.stringify(this._settings);
  }

  parseResult(result: { status: number | string; response: string }): Response {
    let response = new Response();
    const data = JSON.parse(result.response);

    if (typeof result.status === 'string') {
      result.status = Number.parseInt(result.status, 10);
    }

    switch (result.status) {
      case 200:
        response = new SuccessResponse();
        break;
      case 202:
        response = new PayloadResponse();
        break;
      case 400:
        response = new ErrorResponse();
        break;
      case 401:
      case 403:
        response = new NoAccessResponse();
        break;
      case 402:
        response = new PaymentDeclinedResponse();
        break;
      case 404:
      case 405:
      case 406:
        response = new NotFoundResponse();
        break;
      case 408:
        response = new TimeoutResponse();
        break;
      case 412:
      case 418:
        response = new PreconditionalResponse();
        break;
      case 422:
        response = new InputErrorResponse();
        break;
      case 500:
        response = new FailureResponse();
        break;
      default:
        if (result.status > 500) {
          response = new NetworkFailureResponse();
        }
        break;
    }

    response.setStatus(result.status);
    response.action =
      (data === null || data === undefined ? undefined : data.action) || null;
    response.success =
      (data === null || data === undefined ? undefined : data.success) || false;
    response.message =
      (data === null || data === undefined ? undefined : data.message) || null;
    response.data =
      (data === null || data === undefined ? undefined : data.data) || null;
    response.errors =
      (data === null || data === undefined ? undefined : data.errors) || null;

    return response;
  }
}

class Transaction extends ServiceBehaviour {
  /**
   * Send and proccesing SALE transaction
   *
   * @param transaction
   * @return
   * @throws InvalidCredentialsException
   */
  async doSale(transaction: SaleTransaction): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.transactionDoSale(
        this.getSettings(),
        transaction.toJson()
      )
    );
  }

  /**
   * Send and proccesing AUTH transaction
   *
   * @param transaction
   * @return
   * @throws InvalidCredentialsException
   */
  async doAuth(transaction: AuthTransaction): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.transactionDoAuth(
        this.getSettings(),
        transaction.toJson()
      )
    );
  }

  /**
   * Send and proccesing CAPTURE transaction
   *
   * @param transaction
   * @return
   * @throws InvalidCredentialsException
   */
  async doCapture(transaction: CaptureTransaction): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.transactionDoCapture(
        this.getSettings(),
        transaction.toJson()
      )
    );
  }

  /**
   * Send and proccesing VOID transaction
   *
   * @param transaction
   * @return
   * @throws InvalidCredentialsException
   */
  async doVoid(transaction: VoidTransaction): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.transactionDoVoid(
        this.getSettings(),
        transaction.toJson()
      )
    );
  }

  /**
   * Send and proccesing SALE transaction
   *
   * @param transaction
   * @return
   * @throws InvalidCredentialsException
   */
  async getStatus(transaction: StatusTransaction): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.transactionGetStatus(
        this.getSettings(),
        transaction.toJson()
      )
    );
  }

  /**
   * Verify a payment hash and returns true if payment response is not modified
   *
   * @param hash
   * @param order_id
   * @param secret
   * @return
   */
  verifyPaymentHash(hash: string, order_id: string, secret: string): boolean {
    return new BaseTransaction(this._settings).verifyPaymentHash(
      hash,
      order_id,
      secret
    );
  }
}

class Tokenization extends ServiceBehaviour {
  /**
   * Vault credit/debit card and obtain a token card identifier (T-* format)
   *
   * @param card
   * @return
   * @throws InvalidCredentialsException
   */
  async vaultCard(card: CardTokenization): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.tokenizationVaultCard(
        this.getSettings(),
        card.toJson()
      )
    );
  }

  /**
   * Update credit/debit card by token card identifier
   *
   * @param token
   * @param card
   * @return
   * @throws InvalidCredentialsException
   */
  async updateCard(token: string, card: CardTokenization): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.tokenizationUpdateCard(
        this.getSettings(),
        token,
        card.toJson()
      )
    );
  }

  /**
   * Show credit/debit card metadata by token card identifier
   *
   * @param token
   * @return
   * @throws InvalidCredentialsException
   */
  async showCard(token: string): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.tokenizationShowCard(this.getSettings(), token)
    );
  }

  /**
   * Show credit/debit card metadata by token card identifier
   *
   * @param token
   * @return
   * @throws InvalidCredentialsException
   */
  async showCards(tokens: string[]): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.tokenizationShowCards(
        this.getSettings(),
        tokens.join('|')
      )
    );
  }

  /**
   * Delete credit/debit card metadata by token card identifier
   *
   * @param token
   * @return
   * @throws InvalidCredentialsException
   */
  async deleteCard(token: string): Promise<Response> {
    return this.parseResult(
      await ReactNativePlugin.tokenizationDeleteCard(this.getSettings(), token)
    );
  }
}

export { Transaction, Tokenization };
