/*
 This file is part of GNU Anastasis
 (C) 2021-2022 Anastasis SARL

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

 GNU Anastasis 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 Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License along with
 GNU Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
import { AuthenticationProviderStatus } from "@gnu-taler/anastasis-core";
import InvalidState from "../../../components/InvalidState.js";
import NoReducer from "../../../components/NoReducer.js";
import { Notification } from "../../../components/Notifications.js";
import { compose, StateViewMap } from "../../../utils/index.js";
import useComponentState from "./state.js";
import { WithoutProviderType, WithProviderType } from "./views.js";

export type AuthProvByStatusMap = Record<
  AuthenticationProviderStatus["status"],
  (AuthenticationProviderStatus & { url: string })[]
>;

export type State = NoReducer | InvalidState | WithType | WithoutType;

export interface NoReducer {
  status: "no-reducer";
}
export interface InvalidState {
  status: "invalid-state";
}

interface CommonProps {
  addProvider?: () => Promise<void>;
  deleteProvider: (url: string) => Promise<void>;
  authProvidersByStatus: AuthProvByStatusMap;
  error: string | undefined;
  onCancel: () => Promise<void>;
  testing: boolean;
  setProviderURL: (url: string) => Promise<void>;
  providerURL: string;
  errors: string | undefined;
  notifications: Notification[];
}

export interface WithType extends CommonProps {
  status: "with-type";
  providerLabel: string;
}
export interface WithoutType extends CommonProps {
  status: "without-type";
}

const map: StateViewMap<State> = {
  "no-reducer": NoReducer,
  "invalid-state": InvalidState,
  "with-type": WithProviderType,
  "without-type": WithoutProviderType,
};

export default compose("AddingProviderScreen", useComponentState, map);

const providerResponseCache = new Map<string, any>(); // `any` is the return type of res.json()
export async function testProvider(
  url: string,
  expectedMethodType?: string,
): Promise<void> {
  const testFatalPrefix = `Encountered a fatal error whilst testing the provider ${url}`;
  let configUrl = "";
  try {
    configUrl = new URL("config", url).href;
  } catch (error) {
    throw new Error(`${testFatalPrefix}: Invalid Provider URL: ${url}
Error: ${error}`);
  }
  // TODO: look into using core.getProviderInfo :)
  const providerHasUrl = providerResponseCache.has(url);
  const json = providerHasUrl
    ? providerResponseCache.get(url)
    : await fetch(configUrl)
        .catch((error) => {
          throw new Error(`${testFatalPrefix}: Could not connect: ${error}
Please check the URL.`);
        })
        .then(async (response) => {
          if (!response.ok)
            throw new Error(
              `${testFatalPrefix}: The server ${response.url} responded with a non-2xx response.`,
            );
          try {
            return await response.json();
          } catch (error) {
            throw new Error(
              `${testFatalPrefix}: The server responded with malformed JSON.\nError: ${error}`,
            );
          }
        });
  if (typeof json !== "object")
    throw new Error(
      `${testFatalPrefix}: Did not get an object after decoding.`,
    );
  if (!("name" in json) || json.name !== "anastasis") {
    throw new Error(
      `${testFatalPrefix}: The provider does not appear to be an Anastasis provider. Please check the provider's URL.`,
    );
  }
  if (!("methods" in json) || !Array.isArray(json.methods)) {
    throw new Error(
      "This provider doesn't have authentication method. Please check the provider's URL and ensure it is properly configured.",
    );
  }
  if (!providerHasUrl) providerResponseCache.set(url, json);
  if (!expectedMethodType) {
    return;
  }
  let found = false;
  for (let i = 0; i < json.methods.length && !found; i++) {
    found = json.methods[i].type === expectedMethodType;
  }
  if (!found) {
    throw new Error(
      `${testFatalPrefix}: This provider does not support authentication method ${expectedMethodType}`,
    );
  }
  return;
}
