/**
 * Copyright (c) OpenLens Authors. All rights reserved.
 * Licensed under MIT License. See LICENSE in root directory for more information.
 */
import type { RenderResult } from "@testing-library/react";
import { fireEvent } from "@testing-library/react";
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest";
import type { ExecFile } from "../../common/fs/exec-file.injectable";
import execFileInjectable from "../../common/fs/exec-file.injectable";
import helmBinaryPathInjectable from "../../main/helm/helm-binary-path.injectable";
import getActiveHelmRepositoriesInjectable from "../../main/helm/repositories/get-active-helm-repositories/get-active-helm-repositories.injectable";
import type { HelmRepo } from "../../common/helm/helm-repo";
import requestPublicHelmRepositoriesInjectable from "./child-features/preferences/renderer/adding-of-public-helm-repository/public-helm-repositories/request-public-helm-repositories.injectable";
import isPathInjectable from "../../renderer/components/input/validators/is-path.injectable";
import showSuccessNotificationInjectable from "../../renderer/components/notifications/show-success-notification.injectable";
import showErrorNotificationInjectable from "../../renderer/components/notifications/show-error-notification.injectable";
import type { AsyncResult } from "@k8slens/utilities";
import { testUsingFakeTime } from "../../test-utils/use-fake-time";

describe("add custom helm repository in preferences", () => {
  let builder: ApplicationBuilder;
  let showSuccessNotificationMock: jest.Mock;
  let showErrorNotificationMock: jest.Mock;
  let rendered: RenderResult;
  let execFileMock: AsyncFnMock<ExecFile>;
  let getActiveHelmRepositoriesMock: AsyncFnMock<() => AsyncResult<HelmRepo[]>>;

  beforeEach(async () => {
    jest.useFakeTimers();

    builder = getApplicationBuilder();

    testUsingFakeTime("2021-01-01 12:00:00");

    execFileMock = asyncFn();
    getActiveHelmRepositoriesMock = asyncFn();
    showSuccessNotificationMock = jest.fn();
    showErrorNotificationMock = jest.fn();

    builder.beforeApplicationStart(({ mainDi }) => {
      mainDi.override(getActiveHelmRepositoriesInjectable, () => getActiveHelmRepositoriesMock);
      mainDi.override(execFileInjectable, () => execFileMock);
      mainDi.override(helmBinaryPathInjectable, () => "some-helm-binary-path");
    });

    builder.beforeWindowStart(({ windowDi }) => {
      windowDi.override(requestPublicHelmRepositoriesInjectable, () => async () => []);
      windowDi.override(showSuccessNotificationInjectable, () => showSuccessNotificationMock);
      windowDi.override(showErrorNotificationInjectable, () => showErrorNotificationMock);

      // TODO: Figure out how to make async validators unit testable
      windowDi.override(isPathInjectable, () => ({ debounce: 0, validate: async () => {} }));
    });

    rendered = await builder.render();
  });

  describe("when navigating to preferences containing helm repositories", () => {
    beforeEach(async () => {
      builder.preferences.navigate();
      builder.preferences.navigation.click("kubernetes");
    });

    it("renders", () => {
      expect(rendered.baseElement).toMatchSnapshot();
    });

    describe("when active repositories resolve", () => {
      beforeEach(async () => {
        await Promise.all([
          getActiveHelmRepositoriesMock.resolve({
            callWasSuccessful: true,
            response: [{
              name: "Some active repository",
              url: "some-url",
              cacheFilePath: "/some-cache-file-for-active",
            }],
          }),
        ]);
      });

      it("renders", () => {
        expect(rendered.baseElement).toMatchSnapshot();
      });

      describe("when selecting to add custom repository", () => {
        beforeEach(() => {
          const button = rendered.getByTestId("add-custom-helm-repo-button");

          fireEvent.click(button);
        });

        it("renders", () => {
          expect(rendered.baseElement).toMatchSnapshot();
        });

        it("shows dialog", () => {
          expect(
            rendered.queryByTestId("add-custom-helm-repository-dialog"),
          ).toBeInTheDocument();
        });

        // TODO: Figure out how to close dialog by clicking outside of it
        xdescribe("when closing the dialog by clicking outside", () => {
          beforeEach(() => {});

          it("renders", () => {
            expect(rendered.baseElement).toMatchSnapshot();
          });

          it("does not show dialog anymore", () => {
            expect(
              rendered.queryByTestId("add-custom-helm-repository-dialog"),
            ).not.toBeInTheDocument();
          });
        });

        describe("when closing the dialog by clicking cancel", () => {
          beforeEach(() => {
            const button = rendered.getByTestId("custom-helm-repository-cancel-button");

            fireEvent.click(button);
          });

          it("renders", () => {
            expect(rendered.baseElement).toMatchSnapshot();
          });

          it("does not show dialog anymore", () => {
            expect(
              rendered.queryByTestId("add-custom-helm-repository-dialog"),
            ).not.toBeInTheDocument();
          });
        });

        describe("when inputted minimal options for the repository", () => {
          beforeEach(() => {
            getActiveHelmRepositoriesMock.mockClear();

            const nameInput = rendered.getByTestId("custom-helm-repository-name-input");

            fireEvent.change(nameInput, { target: { value: "some-custom-repository" }});

            const urlInput = rendered.getByTestId("custom-helm-repository-url-input");

            fireEvent.change(urlInput, { target: { value: "http://some.url" }});
          });

          it("renders", () => {
            expect(rendered.baseElement).toMatchSnapshot();
          });

          describe("when submitted and some time passes", () => {
            beforeEach(() => {
              const submitButton = rendered.getByTestId("custom-helm-repository-submit-button");

              fireEvent.click(submitButton);

              // TODO: Remove when debounce is removed from WizardStep.submit
              jest.runOnlyPendingTimers();
            });

            it("renders", () => {
              expect(rendered.baseElement).toMatchSnapshot();
            });

            it("adds the repository", () => {
              expect(execFileMock).toHaveBeenCalledWith(
                "some-helm-binary-path",
                ["repo", "add", "some-custom-repository", "http://some.url"],
                {
                  maxBuffer: 34359738368,
                  env: {},
                },
              );
            });

            it("does not reload active repositories yet", () => {
              expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
            });

            it("does not show notification yet", () => {
              expect(showSuccessNotificationMock).not.toHaveBeenCalled();
            });

            describe("when activation rejects", () => {
              beforeEach(async () => {
                await execFileMock.resolve({
                  callWasSuccessful: false,
                  error: Object.assign(new Error("Some error"), {
                    stderr: "",
                  }),
                });
              });

              it("renders", () => {
                expect(rendered.baseElement).toMatchSnapshot();
              });

              it("shows error notification", () => {
                expect(showErrorNotificationMock).toHaveBeenCalledWith(
                  "Some error",
                );
              });

              it("does not show success notification", () => {
                expect(showSuccessNotificationMock).not.toHaveBeenCalled();
              });

              it("does not show dialog anymore", () => {
                expect(rendered.queryByTestId("add-custom-helm-repository-dialog")).not.toBeInTheDocument();
              });

              it("does not reload active repositories", () => {
                expect(getActiveHelmRepositoriesMock).not.toHaveBeenCalled();
              });
            });

            describe("when activation resolves with success", () => {
              beforeEach(async () => {
                await execFileMock.resolveSpecific(
                  [
                    "some-helm-binary-path",
                    ["repo", "add", "some-custom-repository", "http://some.url"],
                  ],
                  {
                    callWasSuccessful: true,
                    response: "",
                  },
                );
              });

              it("renders", () => {
                expect(rendered.baseElement).toMatchSnapshot();
              });

              it("does not show dialog anymore", () => {
                expect(rendered.queryByTestId("add-custom-helm-repository-dialog")).not.toBeInTheDocument();
              });

              it("reloads active repositories", () => {
                expect(getActiveHelmRepositoriesMock).toHaveBeenCalled();
              });

              it("shows success notification", () => {
                expect(showSuccessNotificationMock).toHaveBeenCalledWith(
                  "Helm repository some-custom-repository has been added.",
                );
              });

              describe("when adding custom repository again", () => {
                beforeEach(() => {
                  const button = rendered.getByTestId("add-custom-helm-repo-button");

                  fireEvent.click(button);
                });

                it("renders", () => {
                  expect(rendered.baseElement).toMatchSnapshot();
                });

                it("repository name is empty", () => {
                  const input = rendered.getByTestId("custom-helm-repository-name-input") as HTMLInputElement;

                  expect(input.value).toBe("");
                });

                it("repository url is empty", () => {
                  const input = rendered.getByTestId("custom-helm-repository-url-input") as HTMLInputElement;

                  expect(input.value).toBe("");
                });
              });
            });
          });

          describe("when showing the maximal options", () => {
            beforeEach(() => {
              const button = rendered.getByTestId("toggle-maximal-options-for-custom-helm-repository-button");

              fireEvent.click(button);
            });

            it("shows maximal options", () => {
              const maximalOptions = rendered.getByTestId("maximal-options-for-custom-helm-repository-dialog");

              expect(maximalOptions).toBeInTheDocument();
            });

            it("renders", () => {
              expect(rendered.baseElement).toMatchSnapshot();
            });

            it("given closing the dialog, when reopening the dialog, still shows maximal options", () => {
              const cancelButton = rendered.getByTestId("custom-helm-repository-cancel-button");

              fireEvent.click(cancelButton);

              const openButton = rendered.getByTestId("add-custom-helm-repo-button");

              fireEvent.click(openButton);

              const maximalOptions = rendered.getByTestId("maximal-options-for-custom-helm-repository-dialog");

              expect(maximalOptions).toBeInTheDocument();
            });

            describe("when hiding maximal options", () => {
              beforeEach(() => {
                const button = rendered.getByTestId("toggle-maximal-options-for-custom-helm-repository-button");

                fireEvent.click(button);
              });

              it("renders", () => {
                expect(rendered.baseElement).toMatchSnapshot();
              });

              it("does not show maximal options anymore", () => {
                const maximalOptions = rendered.queryByTestId("maximal-options-for-custom-helm-repository-dialog");

                expect(maximalOptions).not.toBeInTheDocument();
              });
            });

            describe("when inputted maximal options", () => {
              beforeEach(async () => {
                [
                  { selector: "username-input", value: "some-username" },
                  { selector: "password-input", value: "some-password" },
                  { selector: "ca-cert-file-input", value: "some-ca-cert-file" },
                  { selector: "cert-file-input", value: "some-cert-file" },
                  { selector: "key-file-input", value: "some-key-file" },
                ].forEach(({ selector, value }) => {
                  const input = rendered.getByTestId(`custom-helm-repository-${selector}`);

                  fireEvent.change(input, { target: { value }});
                });

                const checkbox = rendered.getByTestId(`custom-helm-repository-verify-tls-input`);

                fireEvent.click(checkbox);

                jest.runOnlyPendingTimers();
              });

              it("renders", () => {
                expect(rendered.baseElement).toMatchSnapshot();
              });

              it("when submitted and some time passes, adds the repository with maximal options", () => {
                const submitButton = rendered.getByTestId("custom-helm-repository-submit-button");

                fireEvent.click(submitButton);

                // TODO: Remove when debounce is removed from WizardStep.submit
                jest.runOnlyPendingTimers();

                expect(execFileMock).toHaveBeenCalledWith(
                  "some-helm-binary-path",
                  [
                    "repo",
                    "add",
                    "some-custom-repository",
                    "http://some.url",
                    "--insecure-skip-tls-verify",
                    "--username",
                    "some-username",
                    "--password",
                    "some-password",
                    "--ca-file",
                    "some-ca-cert-file",
                    "--key-file",
                    "some-key-file",
                    "--cert-file",
                    "some-cert-file",
                  ],
                  {
                    maxBuffer: 34359738368,
                    env: {},
                  },
                );
              });
            });
          });
        });
      });
    });
  });
});
