import {
  initState,
  reducer,
  /*
  distribute,
  getActionPlan,
  predictOptimisticState,
  */
  updateAllProgress,
  getNextAppOp,
} from "./logic";
import { /*runAll,*/ runOneAppOp } from "./runner";
import { deviceInfo155, mockListAppsResult, mockExecWithInstalledContext } from "./mock";
// import { prettyActionPlan, prettyInstalled } from "./formatting";
import { setEnv } from "@ledgerhq/live-env";
import { Action } from "./types";
import { firstValueFrom } from "rxjs";

setEnv("MANAGER_INSTALL_DELAY", 0);
// TODO reactivate this test after bitcoin 2.1.0 nano app

/*
const scenarios = [
  {
    name: "wipe installed apps",
    apps: "Bitcoin Legacy, Ethereum, Litecoin, Dogecoin, Ethereum Classic, XRP",
    installed: "Bitcoin Legacy, Litecoin, XRP, Ethereum Classic, Ethereum",
    actions: [
      {
        dispatch: {
          type: "wipe",
        },
        expectPlan:
          "-Litecoin, -Bitcoin Legacy, -XRP, -Ethereum Classic, -Ethereum",
        expectInstalled: "",
      },
    ],
  },
  {
    name: "install an app install its dep",
    apps: "Bitcoin Legacy, Litecoin, Dogecoin",
    installed: "",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "Dogecoin",
        },
        expectPlan: "+Bitcoin Legacy, +Dogecoin",
        expectInstalled: "Bitcoin Legacy, Dogecoin",
      },
      {
        dispatch: {
          type: "install",
          name: "Litecoin",
        },
        expectPlan: "+Litecoin",
        expectInstalled: "Bitcoin Legacy, Dogecoin, Litecoin",
      },
    ],
  },
  {
    name: "install and uninstall an app",
    apps: "XRP",
    installed: "",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "XRP",
        },
        expectPlan: "+XRP",
        expectInstalled: "XRP",
      },
      {
        dispatch: {
          type: "uninstall",
          name: "XRP",
        },
        expectPlan: "-XRP",
        expectInstalled: "",
      },
    ],
  },
  {
    name: "install an app install its dep",
    apps: "Bitcoin Legacy, Litecoin, Dogecoin",
    installed: "",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "Dogecoin",
        },
        expectPlan: "+Bitcoin Legacy, +Dogecoin",
        expectInstalled: "Bitcoin Legacy, Dogecoin",
      },
      {
        dispatch: {
          type: "install",
          name: "Litecoin",
        },
        expectPlan: "+Litecoin",
        expectInstalled: "Bitcoin Legacy, Dogecoin, Litecoin",
      },
    ],
  },
  {
    name: "uninstall an app that have deps",
    apps: "Bitcoin Legacy, Litecoin, Dogecoin",
    installed: "Bitcoin Legacy, Litecoin, Dogecoin",
    actions: [
      {
        dispatch: {
          type: "uninstall",
          name: "Bitcoin Legacy",
        },
        expectPlan: "-Litecoin, -Dogecoin, -Bitcoin Legacy",
        expectInstalled: "",
      },
    ],
  },
  {
    name: "install existing is noop",
    apps: "Bitcoin Legacy",
    installed: "Bitcoin Legacy",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "Bitcoin Legacy",
        },
        expectPlan: "",
        expectInstalled: "Bitcoin Legacy",
      },
    ],
  },
  {
    name: "uninstall non-existing is noop",
    apps: "Bitcoin Legacy",
    installed: "",
    actions: [
      {
        dispatch: {
          type: "uninstall",
          name: "Bitcoin Legacy",
        },
        expectPlan: "",
        expectInstalled: "",
      },
    ],
  },
  {
    name: "install an outdated app",
    apps: "Bitcoin Legacy",
    installed: "Bitcoin Legacy (outdated)",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "Bitcoin Legacy",
        },
        expectPlan: "-Bitcoin Legacy, +Bitcoin Legacy",
        expectInstalled: "Bitcoin Legacy",
      },
    ],
  },
  {
    name: "install an outdated app dep",
    apps: "Bitcoin Legacy, Dogecoin",
    installed: "Bitcoin Legacy (outdated), Dogecoin (outdated)",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "Dogecoin",
        },
        expectPlan: "-Dogecoin, -Bitcoin Legacy, +Bitcoin Legacy, +Dogecoin",
        expectInstalled: "Bitcoin Legacy, Dogecoin",
      },
    ],
  },
  {
    name: "install an app with outdated dep",
    apps: "Bitcoin Legacy, Litecoin, Dogecoin",
    installed: "Bitcoin Legacy (outdated), Litecoin (outdated)",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "Dogecoin",
        },
        expectPlan:
          "-Litecoin, -Bitcoin Legacy, +Bitcoin Legacy, +Litecoin, +Dogecoin",
        expectInstalled: "Bitcoin Legacy, Litecoin, Dogecoin",
      },
    ],
  },
  {
    name: "install an outdated app with dependents",
    apps: "Bitcoin Legacy, Dogecoin",
    installed: "Bitcoin Legacy (outdated), Dogecoin (outdated)",
    actions: [
      {
        dispatch: {
          type: "install",
          name: "Bitcoin Legacy",
        },
        expectPlan: "-Dogecoin, -Bitcoin Legacy, +Bitcoin Legacy, +Dogecoin",
        expectInstalled: "Bitcoin Legacy, Dogecoin",
      },
    ],
  },
  {
    name: "update all will reinstall the outdated",
    apps: "Bitcoin Legacy, Litecoin, Ethereum",
    installed: "Bitcoin Legacy (outdated), Litecoin (outdated), Ethereum",
    actions: [
      {
        dispatch: {
          type: "updateAll",
        },
        expectPlan: "-Litecoin, -Bitcoin Legacy, +Bitcoin Legacy, +Litecoin",
        expectInstalled: "Ethereum, Bitcoin Legacy, Litecoin",
      },
    ],
  },
  {
    name: "update all still works with unknown apps",
    apps: "Bitcoin Legacy, Litecoin, Ethereum",
    installed:
      "Bitcoin Legacy (outdated), Litecoin (outdated), Ethereum, Unknown",
    actions: [
      {
        dispatch: {
          type: "updateAll",
        },
        expectPlan: "-Litecoin, -Bitcoin Legacy, +Bitcoin Legacy, +Litecoin",
        expectInstalled: "Ethereum, Unknown, Bitcoin Legacy, Litecoin",
      },
    ],
  },
  {
    name: "install and uninstall will undo (if top level dep)",
    apps: "Bitcoin Legacy",
    installed: "",
    actions: [
      {
        dispatch: [
          {
            type: "install",
            name: "Bitcoin Legacy",
          },
          {
            type: "uninstall",
            name: "Bitcoin Legacy",
          },
        ],
        expectPlan: "",
        expectInstalled: "",
      },
    ],
  },
  {
    name: "order is preserved in install action plan",
    apps: "Bitcoin Legacy, XRP, Ethereum, Ethereum Classic, Dogecoin, Zcash",
    installed: "",
    actions: [
      {
        dispatch: [
          {
            type: "install",
            name: "XRP",
          },
          {
            type: "install",
            name: "Ethereum Classic",
          },
          {
            type: "install",
            name: "Dogecoin",
          },
          {
            type: "install",
            name: "Zcash",
          },
        ],
        expectPlan:
          "+XRP, +Ethereum, +Ethereum Classic, +Bitcoin Legacy, +Dogecoin, +Zcash",
        expectInstalled:
          "XRP, Ethereum, Ethereum Classic, Bitcoin Legacy, Dogecoin, Zcash",
      },
    ],
  },
  {
    name: "order is preserved in uninstall action plan",
    apps: "Bitcoin Legacy, XRP, Ethereum, Ethereum Classic, Dogecoin, Zcash",
    installed:
      "XRP, Ethereum, Ethereum Classic, Bitcoin Legacy, Dogecoin, Zcash",
    actions: [
      {
        dispatch: [
          {
            type: "uninstall",
            name: "XRP",
          },
          {
            type: "uninstall",
            name: "Ethereum",
          },
          {
            type: "uninstall",
            name: "Dogecoin",
          },
          {
            type: "uninstall",
            name: "Bitcoin Legacy",
          },
        ],
        expectPlan:
          "-XRP, -Ethereum Classic, -Ethereum, -Dogecoin, -Bitcoin Legacy",
        expectInstalled: "",
      },
    ],
  },
];

scenarios.forEach((scenario) => {
  test("Scenario: " + scenario.name, async () => {
    let state = initState(
      mockListAppsResult(scenario.apps, scenario.installed, deviceInfo155)
    );
    expect(prettyActionPlan(getActionPlan(state))).toBe("");

    for (const action of scenario.actions) {
      state = (<any[]>[]).concat(action.dispatch).reduce(reducer, state);
      expect(prettyActionPlan(getActionPlan(state))).toBe(action.expectPlan);
      const optimisticState = predictOptimisticState(state);
      state = await firstValueFrom(runAll(
        state,
        mockExecWithInstalledContext(state.installed)
      ));

      if (action.expectInstalled) {
        expect(prettyInstalled(state.installed)).toBe(action.expectInstalled);
      }
      state.currentProgressSubject = null;
      expect(state).toEqual(optimisticState);
      expect(prettyActionPlan(getActionPlan(state))).toBe("");
      const d: any = distribute(state);
      d.apps = d.apps.map(({ currency, ...rest }) => ({
        ...rest,
        currencyId: currency && currency.id,
      }));
      expect(d).toMatchSnapshot();
    }
  });
});

test("appsToRestore", async () => {
  const state = initState(
    mockListAppsResult(
      "Bitcoin Legacy, XRP, Ethereum, Ethereum Classic, Dogecoin, Zcash",
      "Bitcoin Legacy, Zcash",
      deviceInfo155
    ),
    ["Bitcoin Legacy", "XRP", "Dogecoin", "Zcash", "Ethereum Classic"]
  );
  expect(prettyActionPlan(getActionPlan(state))).toBe(
    "+XRP, +Dogecoin, +Ethereum, +Ethereum Classic"
  );
});
*/
/*
test("a lock error that occurs will not cancel the queue, another error will", () => {
  let state = initState(
    mockListAppsResult(
      "Bitcoin Legacy, XRP, Ethereum, Ethereum Classic, Dogecoin, Zcash",
      "",
      deviceInfo155
    )
  );

  state = [
    { type: "install", name: "Dogecoin" },
    { type: "install", name: "Ethereum Classic" }
  ].reduce(reducer, state);

  const plan = getActionPlan(state);

  state = reducer(state, {
    type: "onRunnerEvent",
    event: {
      type: "runError",
      appOp: plan[0],
      error: new ManagerDeviceLockedError()
    }
  });

  expect(getActionPlan(state)).toEqual(plan);
  expect(state.currentError).toEqual({
    appOp: plan[0],
    error: new ManagerDeviceLockedError()
  });

  state = reducer(state, { type: "recover" });
  expect(state.currentError).toBe(null);

  state = reducer(state, {
    type: "onRunnerEvent",
    event: {
      type: "runError",
      appOp: plan[0],
      error: new Error()
    }
  });

  expect(getActionPlan(state)).toEqual([]);
});
*/
test("global progress", async () => {
  let state = initState(
    mockListAppsResult(
      "Bitcoin Legacy, XRP, Ethereum, Ethereum Classic, Dogecoin, Zcash",
      "Bitcoin Legacy (outdated), Ethereum (outdated)",
      deviceInfo155,
    ),
  );
  expect(updateAllProgress(state)).toBe(1);
  state = [
    <Action>{
      type: "updateAll",
    },
  ].reduce(reducer, state);
  expect(updateAllProgress(state)).toBe(0);
  let next;
  let i = 0;
  const total = 4;

  while ((next = getNextAppOp(state))) {
    state = await firstValueFrom(
      runOneAppOp({ state, appOp: next, exec: mockExecWithInstalledContext(state.installed) }),
    );
    expect(updateAllProgress(state)).toBe(++i / total);
  }

  expect(i).toBe(total);
  expect(updateAllProgress(state)).toBe(1);
});
