/*
 This file is part of GNU Taler
 (C) 2022-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 {
  AccessToken,
  Codec,
  buildCodecForObject,
  codecForAccessToken,
  codecForList,
  codecForString,
  codecOptionalDefault,
} from "@gnu-taler/taler-util";
import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser";
import { usePreferences } from "../context/preferences.js";
import { codecForNumber } from "@gnu-taler/taler-util";
import { useEffect } from "preact/compat";

export type SessionState = {
  accessToken: AccessToken;
  testAccounts: PrivPub[];
  time: number;
};

type PrivPub = {
  priv: string;
  pub: string;
};

export const codecForSessionState = (): Codec<SessionState> =>
  buildCodecForObject<SessionState>()
    .property("accessToken", codecForAccessToken())
    .property("time", codecOptionalDefault(codecForNumber(), 0))
    .property(
      "testAccounts",
      codecOptionalDefault(codecForList(codecForPrivPub()), []),
    )
    .build("SessionState");
export const codecForPrivPub = (): Codec<PrivPub> =>
  buildCodecForObject<PrivPub>()
    .property("priv", codecForString())
    .property("pub", codecForString())
    .build("SessionState");

export interface SessionStateHandler {
  state: SessionState | undefined;
  start(s: AccessToken): void;
}

// FIXME: adde expiration 1 week
const SESSION_STATE_KEY = buildStorageKey(
  "kyc-session",
  codecForSessionState(),
);

const SESSION_EXPIRATION_TIME_MS = 1000 * 60 * 60 * 24 * 7; // 1 week

/**
 * Return getters and setters for
 * login credentials and backend's
 * base URL.
 */
export function useSessionState(): SessionStateHandler {
  const { value: state, update, reset } = useLocalStorage(SESSION_STATE_KEY);
  const [pref] = usePreferences();
  const urlToken = getAccessTokenFromURL();

  // Normally, if the user accesses the SPA with an access token in the URL then
  // switch to that access token.
  // If we're in debug mode, preserve the access token.
  let accessToken: AccessToken | undefined;
  if (pref.showDebugInfo && state) {
    console.log(`Preserving token, as debug mode is enabled`);
    accessToken = state.accessToken;
  } else {
    accessToken = urlToken ?? state?.accessToken;
  }
  useEffect(() => {
    if (state) {
      const diff = new Date().getTime() - state.time;
      if (diff > SESSION_EXPIRATION_TIME_MS) {
        reset();
      }
    }
  }, []);
  return {
    state: accessToken
      ? {
          accessToken,
          testAccounts: state?.testAccounts ?? [],
          time: state?.time ?? new Date().getTime(),
        }
      : undefined,
    start(accessToken) {
      update({ accessToken, testAccounts: [], time: new Date().getTime() });
    },
  };
}

const ACCESS_TOKEN_REGEX = new RegExp("[A-Z0-9]{52}");
/**
 * by how the exchange serves the SPA
 * /kyc-spa/KXAFXEWM7E3EJSYD9GJ30FYK1C17AKZWV119ZJA3XGPBBMZFJ2C0#/start
 *
 * by how dev.mjs serves the SPA
 * /app/?token=KXAFXEWM7E3EJSYD9GJ30FYK1C17AKZWV119ZJA3XGPBBMZFJ2C0#/start
 * @returns
 */
function getAccessTokenFromURL(): AccessToken | undefined {
  if (typeof window === "undefined") return undefined;
  const paths = window.location.pathname.split("/");
  if (paths.length < 3) return undefined;
  const path = paths[2];
  if (path && ACCESS_TOKEN_REGEX.test(path)) {
    return path as AccessToken;
  }
  const param = new URLSearchParams(window.location.search).get("token");
  if (param && ACCESS_TOKEN_REGEX.test(param)) {
    return param as AccessToken;
  }
  return undefined;
}
