import { getResponseBodyOrError } from '$lib/api/utils';
import { readUser } from '$lib/api/endpoints';
import jwt_decode from 'jwt-decode';
import { writable } from 'svelte/store';

/**
 * Svelte store to hold the current StoredUser object.
 */
export const storedUser = writable();

// Name of the key holding the auth JWT in localstorage
const jwtKey = 'jwt';

// Name of the key holding the authenticated user in localstorage
const userKey = 'user';
export type StoredUser = {
  id: number,
  email: string,
  isAdmin: boolean,
  sessionExpires: Date
}

/**
  * Saves the specified token in localstorage.
  *
  * @param {string} token - The token to save in localstorage.
  */
function saveTokenToLocalstorage(token: string): void {
  localStorage.setItem(jwtKey, token);
}

/**
  * Retrieves and returns the token, if present, from localstorage.
  */
export function getTokenFromLocalstorage(): string | null {
  return localStorage.getItem(jwtKey);
}

/**
  * Removes the saved token from localstorage.
  */
function clearTokenInLocalstorage(): void {
  localStorage.removeItem(jwtKey);
}

/**
  * Saves the specified StoredUser object in localstorage.
  *
  * @param {StoredUser} user - The user to write to localstorage.
  */
function saveUserToLocalstorage(user: StoredUser): void {
  localStorage.setItem(userKey, JSON.stringify(user));
}

/**
  * Retrieves and returns the user, if present, from localstorage.
  */
function getUserFromLocalstorage(): StoredUser | null {
  let item: string | null = localStorage.getItem(userKey);
  if (typeof item !== 'string') {
    return null;
  }
  return JSON.parse(item) as StoredUser;
}

/**
  * Removes the saved user from localstorage.
  */
function clearUserInLocalstorage(): void {
  localStorage.removeItem(userKey);
}

/**
  * Sends an API request containing the specified login credentials to the backend,
  * and returns the JWT retrieved from the response on sucess.
  *
  * @param {string} email - The email address of the user whose JWT to retrieve.
  * @param {string} password - The password used to authenticate the user whose JWT is being retrieved.
  * @throws {Error} - If the API request failed or the supplied credentials were invalid.
  */
async function requestJwt(email: string, password: string): Promise<string> {
  type Token = {
    token: string
  }

  const response = await fetch('/api/auth/login', {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email: email,
      password: password,
    })
  });

  const responseBody = await getResponseBodyOrError(response) as Token;
  return responseBody.token;
}

/**
 * Loads the user currently saved in localstorage into the storedUser
 * svelte store.
 */
export function loadUserIntoStore(): void {
  storedUser.set(getUserFromLocalstorage());
}

/**
 * Clears the storedUser svelte store.
 */
function clearUserFromStore(): void {
  storedUser.set(null);
}

/**
 * Sends an API request to retrieve the specified user's JWT from the backend,
 * and stores the retrieved token in localstorage. Then, retrieves the authenticated
 * user's basic account information (StoredUser), saves it in localsotrage and in the
 * currentUser svelte store.
 *
 * @param {string} email - The email address of the user whose token and information to retrieve.
 * @param {string} password - The password used to authenticate the user whose token is being retrieved.
 * @throws {Error} - If any API request fails or the supplied credentials were invalid.
 */
export async function login(email: string, password: string): Promise<void> {
  const token = await requestJwt(email, password);

  interface Token {
    sub: number,
    exp: number
  }
  const parsedToken = jwt_decode(token) as Token;
  const userId = parsedToken.sub;

  const user = await readUser(userId, token);

  saveTokenToLocalstorage(token);
  saveUserToLocalstorage({
    id: user.id,
    email: user.email,
    isAdmin: user.is_admin,
    sessionExpires: new Date(parsedToken.exp),
  });
  loadUserIntoStore();
}

/**
 * Purges the currently logged in user's information and token from localstorage
 * and the svelte store.
 */
export function logout(): void {
  clearUserFromStore();
  clearUserInLocalstorage();
  clearTokenInLocalstorage();
}
