import { Observable, of, concat } from "rxjs";
import { scan, tap } from "rxjs/operators";
import { useEffect, useState } from "react";
import type { Action, Device } from "./types";
import type { AppState } from "./app";
import type { DeviceModelId } from "@ledgerhq/devices";
import { log } from "@ledgerhq/logs";
import { Exchange } from "../../exchange/types";
import { Transaction } from "../../generated/types";

type State = {
  completeExchangeResult: Transaction | null | undefined;
  completeExchangeError: Error | null | undefined;
  freezeReduxDevice: boolean;
  completeExchangeRequested: boolean;
  isLoading: boolean;
  estimatedFees: string | undefined;
};

type CompleteExchangeState = AppState & State;
type CompleteExchangeRequest = {
  deviceModelId?: DeviceModelId;
  deviceId?: string;
  provider: string;
  transaction: Transaction;
  binaryPayload: string;
  signature: string;
  exchange: Exchange;
  exchangeType: number;
  rateType?: number;
  swapId?: string;
  rate?: number;
  amountExpectedTo?: number;
};
type Result =
  | {
      completeExchangeResult: Transaction;
    }
  | {
      completeExchangeError: Error;
    };

type CompleteExchangeAction = Action<CompleteExchangeRequest, CompleteExchangeState, Result>;
export type ExchangeRequestEvent =
  | { type: "complete-exchange" }
  | { type: "complete-exchange-requested"; estimatedFees: string }
  | { type: "complete-exchange-error"; error: Error }
  | { type: "complete-exchange-result"; completeExchangeResult: Transaction };

const mapResult = ({
  completeExchangeResult,
  completeExchangeError,
}: CompleteExchangeState): Result | null | undefined =>
  completeExchangeResult
    ? {
        completeExchangeResult,
      }
    : completeExchangeError
      ? {
          completeExchangeError,
        }
      : null;

const initialState: State = {
  completeExchangeResult: null,
  completeExchangeError: null,
  completeExchangeRequested: false,
  freezeReduxDevice: false,
  isLoading: true,
  estimatedFees: undefined,
};

const reducer = (state: State, e: ExchangeRequestEvent) => {
  switch (e.type) {
    case "complete-exchange":
      return {
        ...state,
        completeExchangeStarted: true,
        freezeReduxDevice: true,
      };

    case "complete-exchange-error":
      return {
        ...state,
        completeExchangeError: e.error,
        isLoading: false,
      };

    case "complete-exchange-requested":
      return {
        ...state,
        estimatedFees: e.estimatedFees,
        isLoading: false,
      };
    case "complete-exchange-result":
      return {
        ...state,
        completeExchangeResult: e.completeExchangeResult,
        isLoading: false,
      };
  }
  return state;
};

function useFrozenValue<T>(value: T, frozen: boolean): T {
  const [state, setState] = useState(value);
  useEffect(() => {
    if (!frozen) {
      setState(value);
    }
  }, [value, frozen]);
  return state;
}

export const createAction = (
  completeExchangeExec: (arg0: CompleteExchangeRequest) => Observable<ExchangeRequestEvent>,
): CompleteExchangeAction => {
  const useHook = (
    reduxDevice: Device | null | undefined,
    completeExchangeRequest: CompleteExchangeRequest,
  ): CompleteExchangeState => {
    const [state, setState] = useState(initialState);
    const reduxDeviceFrozen = useFrozenValue(reduxDevice, state?.freezeReduxDevice);

    const { provider, transaction, binaryPayload, signature, exchange, exchangeType, rateType } =
      completeExchangeRequest;

    useEffect(() => {
      const sub = concat(
        of(<ExchangeRequestEvent>{
          type: "complete-exchange",
        }),
        completeExchangeExec({
          deviceId: reduxDeviceFrozen?.deviceId,
          deviceModelId: reduxDeviceFrozen?.modelId,
          provider,
          transaction,
          binaryPayload,
          signature,
          exchange,
          exchangeType,
          rateType,
        }),
      )
        .pipe(
          tap(e => {
            log("actions-completeExchange-event", JSON.stringify(e));
          }),
          scan(reducer, initialState),
        )
        .subscribe(setState);
      return () => {
        sub.unsubscribe();
      };
    }, [
      provider,
      transaction,
      binaryPayload,
      signature,
      exchange,
      exchangeType,
      rateType,
      reduxDeviceFrozen,
    ]);

    return { ...state, device: reduxDeviceFrozen } as CompleteExchangeState;
  };

  return {
    useHook,
    mapResult,
  };
};
