/*
 This file is part of GNU Taler
 (C) 2021-2024 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
import {
  ChallengeResponse,
  HttpStatusCode,
  MerchantAuthMethod,
  TalerError,
  TalerMerchantManagementResultByMethod,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  useChallengeHandler,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { ErrorLoadingMerchant } from "../../../components/ErrorLoadingMerchant.js";
import { Loading } from "../../../components/exception/loading.js";
import { NotificationCard } from "../../../components/menu/index.js";
import { useSessionContext } from "../../../context/session.js";
import {
  useInstanceDetails,
  useManagedInstanceDetails,
} from "../../../hooks/instance.js";
import { usePreference } from "../../../hooks/preference.js";
import { Notification } from "../../../utils/types.js";
import {
  FOREVER_REFRESHABLE_TOKEN,
  LoginPage,
  TEMP_TEST_TOKEN,
} from "../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../notfound/index.js";
import { DetailPage } from "./DetailPage.js";
import { SolveMFAChallenges } from "../../../components/SolveMFA.js";

export interface Props {
  onChange: () => void;
  onCancel: () => void;
}

export default function PasswordPage(props: Props): VNode {
  const { lib, state } = useSessionContext();
  const result = useInstanceDetails();
  const instanceId = state.instance;

  const mfa = useChallengeHandler();
  const [doChangePassword, repeatChangePassword] = mfa.withMfaHandler(
    ({ challengeIds, onChallengeRequired }) =>
      async function changePassword(
        // currentPassword: string | undefined,
        newPassword: string,
      ) {
        // if (currentPassword) {
        // const resp = await lib.instance.createAccessToken(
        //     instanceId,
        //     currentPassword,
        //     TEMP_TEST_TOKEN(i18n.str`Testing password`),
        //   );
        //   if (resp.case === HttpStatusCode.Accepted) {
        //     throw Error("FIXME!!!!");
        //   }
        //   if (resp.type !== "ok") {
        //     throw Error(resp.detail?.hint ?? "The current password is wrong");
        //   }
        // }

        {
          const resp = await lib.instance.updateCurrentInstanceAuthentication(
            state.token,
            {
              password: newPassword,
              method: MerchantAuthMethod.TOKEN,
            },
            { challengeIds },
          );
          if (resp.type === "fail") {
            if (resp.case === HttpStatusCode.Accepted) {
              onChallengeRequired(resp.body);
              return;
            }
            throw Error(resp.detail?.hint ?? "The request failed");
          }
        }

        // const resp = await lib.instance.createAccessToken(
        //   instanceId,
        //   newPassword,
        //   FOREVER_REFRESHABLE_TOKEN(i18n.str`Password changed`),
        // );
        // if (resp.type === "ok") {
        //   logIn(state.instance, resp.body.access_token);
        //   return;
        // } else {
        //   if (resp.case === HttpStatusCode.Accepted) {
        //     throw Error("FIXME!!!!");
        //   }
        //   throw Error(resp.detail?.hint ?? "The new login failed");
        // }
      },
  );
  if (mfa.pendingChallenge) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        onCompleted={repeatChangePassword}
        onCancel={mfa.doCancelChallenge}
      />
    );
  }

  return CommonPassword({ ...props, instanceId }, result, doChangePassword);
}

export function AdminPassword(props: Props & { instanceId: string }): VNode {
  const { lib, state } = useSessionContext();

  const subInstanceLib = lib.subInstanceApi(props.instanceId).instance;
  const result = useManagedInstanceDetails(props.instanceId);

  const instanceId = props.instanceId;

  const mfa = useChallengeHandler();
  const [doChangePassword, repeatChangePassword] = mfa.withMfaHandler(
    ({ challengeIds, onChallengeRequired }) =>
      async function changePassword(
        // currentPassword: string | undefined,
        newPassword: string,
      ) {
        // if (currentPassword) {
        //   const resp = await lib.instance.createAccessToken(
        //     instanceId,
        //     currentPassword,
        //     TEMP_TEST_TOKEN(i18n.str`Testing password for instance ${instanceId}`),
        //   );
        //   if (resp.type !== "ok") {
        //     if (resp.case === HttpStatusCode.Accepted) {
        //       throw Error("FIXME!!!!");
        //     }
        //     throw Error(resp.detail?.hint ?? "The current password is wrong");
        //   }
        // }

        {
          const resp = await lib.instance.updateInstanceAuthentication(
            state.token,
            props.instanceId,
            {
              password: newPassword,
              method: MerchantAuthMethod.TOKEN,
            },
            { challengeIds },
          );
          if (resp.type === "fail") {
            if (resp.case === HttpStatusCode.Accepted) {
              onChallengeRequired(resp.body);
              return;
            }
            throw Error(resp.detail?.hint ?? "The request failed");
          }
        }
        // const resp = await subInstanceLib.createAccessToken(
        //   instanceId,
        //   newPassword,
        //   FOREVER_REFRESHABLE_TOKEN(
        //     i18n.str`Password changed for instance ${instanceId}`,
        //   ),
        // );
        // if (resp.type === "ok") {
        //   return;
        // } else {
        //   if (resp.case === HttpStatusCode.Accepted) {
        //     throw Error("FIXME!!!!");
        //   }
        //   throw Error(resp.detail?.hint ?? "The new login failed");
        // }
      },
  );

  if (mfa.pendingChallenge) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        onCompleted={repeatChangePassword}
        onCancel={mfa.doCancelChallenge}
      />
    );
  }

  return CommonPassword(props, result, doChangePassword);
}

function CommonPassword(
  { onChange, onCancel, instanceId }: Props & { instanceId: string },
  result:
    | TalerMerchantManagementResultByMethod<"getInstanceDetails">
    | TalerError
    | undefined,
  onNewPassword: (
    // oldToken: string | undefined,
    newToken: string,
    challengeIds: undefined | string[],
  ) => Promise<void>,
): VNode {
  const { i18n } = useTranslationContext();
  const { state } = useSessionContext();
  const [notif, setNotif] = useState<Notification | undefined>(undefined);

  if (!result) return <Loading />;
  if (result instanceof TalerError) {
    return <ErrorLoadingMerchant error={result} />;
  }
  if (result.type === "fail") {
    switch (result.case) {
      case HttpStatusCode.Unauthorized: {
        return <LoginPage />;
      }
      case HttpStatusCode.NotFound: {
        return <NotFoundPageOrAdminCreate />;
      }
      default: {
        assertUnreachable(result);
      }
    }
  }

  const adminChangingPwdForAnotherInstance =
    state.isAdmin && state.instance !== instanceId;
  const hasToken =
    result.body.auth.method === MerchantAuthMethod.TOKEN &&
    !adminChangingPwdForAnotherInstance;

  const id = result.body.name;
  return (
    <Fragment>
      <NotificationCard notification={notif} />
      <DetailPage
        onBack={onCancel}
        instanceId={result.body.name}
        hasPassword={hasToken}
        onNewPassword={async (newPassword): Promise<void> => {
          // onNewPassword={async (currentPassword, newPassword): Promise<void> => {
          try {
            // await onNewPassword(currentPassword, newPassword);
            await onNewPassword(newPassword, undefined);
            return onChange();
          } catch (error) {
            return setNotif({
              message: i18n.str`Failed to set new password`,
              type: "ERROR",
              description:
                error instanceof Error ? error.message : String(error),
            });
          }
        }}
      />
    </Fragment>
  );
}
