import { WebsocketConnectionError } from "@ledgerhq/errors";
import axios from "axios";
import WS from "isomorphic-ws";
import { Observable } from "rxjs";
import { getEnv } from "@ledgerhq/live-env";
import serviceStatusApi from "../notifications/ServiceStatusProvider/api/api";

export type TroubleshootStatus = {
  title: string;
  technicalDescription: string;
  status: "success" | "error" | "loading";
  error?: string;
  translationKey: string;
};

type Troubleshoot = {
  title: string;
  technicalDescription: string;
  job: Promise<unknown>;
  translationKey: string;
};

// Run all checks and return. each troubleshoot have a promise that suceed if the underlying job worked.
export function troubleshoot(): Troubleshoot[] {
  // TODO in future, we can delegate a "troubleshoot" per coin implementation.
  return [
    {
      title: "My Ledger services (scriptrunner)",
      translationKey: "troubleshootNetwork.myLedgerServices",
      ...websocketConnects(
        `${getEnv(
          "BASE_SOCKET_URL",
        )}/apps/list?targetId=856686596&perso=perso_11&livecommonversion=27.7.2`, // TODO have a dummy echo endpoint
      ),
    },
    {
      title: "Bitcoin explorers",
      translationKey: "troubleshootNetwork.bitcoinExplorers",
      ...httpGet(getEnv("EXPLORER") + "/blockchain/v4/btc/block/current"),
    },
    {
      title: "Ethereum explorers",
      translationKey: "troubleshootNetwork.ethereumExplorers",
      ...httpGet(getEnv("EXPLORER") + "/blockchain/v4/eth/block/current"),
    },
    {
      title: "Countervalues API",
      translationKey: "troubleshootNetwork.countervaluesApi",
      ...httpGet(`${getEnv("LEDGER_COUNTERVALUES_API")}/v3/spot/simple?froms=bitcoin&to=eur`),
    },
    {
      title: "Status",
      translationKey: "troubleshootNetwork.status",
      technicalDescription: "fetching status",
      job: serviceStatusApi.fetchStatusSummary(),
    },
  ];
}

function httpGet(url) {
  return {
    technicalDescription: "fetching " + url,
    job: axios.get(url, { timeout: 30000 }),
  };
}

function websocketConnects(url) {
  const job = new Promise((resolve, reject) => {
    const ws = new WS(url);
    const timeout = setTimeout(() => {
      ws.close();
      reject(new Error("timeout"));
    }, 30000);
    ws.onopen = () => {
      clearTimeout(timeout);
      resolve(url);
      ws.close();
    };
    ws.onerror = e => {
      reject(e);
    };
    ws.onclose = () => {
      reject(new WebsocketConnectionError("closed"));
    };
  });
  return {
    technicalDescription: "connecting to " + url,
    job,
  };
}

type TroubleshootEvent =
  | {
      type: "init";
      all: TroubleshootStatus[];
    }
  | {
      type: "change";
      status: TroubleshootStatus;
    };

export function troubleshootOverObservable(): Observable<TroubleshootEvent> {
  return new Observable(o => {
    try {
      const all = troubleshoot();
      o.next({
        type: "init",
        all: all.map(s => ({
          title: s.title,
          translationKey: s.translationKey,
          technicalDescription: s.technicalDescription,
          status: "loading",
        })),
      });

      let total = 0;
      all.forEach(s => {
        s.job
          .then(
            () => {
              o.next({
                type: "change",
                status: {
                  title: s.title,
                  translationKey: s.translationKey,
                  technicalDescription: s.technicalDescription,
                  status: "success",
                },
              });
            },
            e => {
              o.next({
                type: "change",
                status: {
                  title: s.title,
                  translationKey: s.translationKey,
                  technicalDescription: s.technicalDescription,
                  status: "error",
                  error: String(e?.message || e),
                },
              });
            },
          )
          .then(() => {
            if (++total === all.length) {
              o.complete();
            }
          });
      });
    } catch (e) {
      o.error(e);
    }
  });
}

export function troubleshootOverObservableReducer(
  state: TroubleshootStatus[],
  event: TroubleshootEvent,
): TroubleshootStatus[] {
  if (event.type === "init") {
    return event.all;
  }
  if (event.type === "change") {
    return state.map(s => (s.title === event.status.title ? event.status : s));
  }
  return state;
}
