// The Lucia project.
// Copyright 2023-2026 DSP, inc.  All rights reserved.
// Authors:
//   chiayin.kuo@dsp.im (chiayin), 2023/1/31
//   imacat.yang@dsp.im (imacat), 2023/9/23
//   cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/**
 * @module stores/acctMgmt Account management store for CRUD
 *   operations on user accounts (admin functionality).
 */

import { defineStore } from 'pinia';
import apiClient from '@/api/client.js';
import apiError from '@/module/apiError';
import { useLoginStore } from '@/stores/login';
import { JUST_CREATE_ACCOUNT_HOT_DURATION_MINS } from '@/constants/constants';

export interface User {
  username: string;
  detail: Record<string, any>;
  name?: string;
  is_admin?: boolean;
  is_sso?: boolean;
  isCurrentLoggedIn: boolean,
  isDeleteHovered?: boolean;
  isRowHovered?: boolean;
  isEditHovered?: boolean;
  isDetailHovered?: boolean;
}

export interface EditDetail {
  newUsername?: string;
  username?: string;
  password: string;
  name: string;
  is_active: boolean;
}

export const useAcctMgmtStore = defineStore('acctMgmtStore', {
  state: () => ({
    allUserAccountList: [] as User[],
    isAcctMenuOpen: false,
    currentViewingUser: {
      username: '',
      detail: {},
    } as User,
    response: {
      deleteAccount: null,
    },
    isOneAccountJustCreate: false, // If an account was just created, display it at the top of the list with a badge
    justCreateUsername: '', // unique username
    shouldUpdateList: false, // Controls whether the list should be refreshed
  }),
  getters: {},
  actions: {
    /**
     * Set related boolean to true
     */
    openAcctMenu() {
      this.isAcctMenuOpen = true;
    },
    /**
     * Set related boolean to false
     */
    closeAcctMenu() {
      this.isAcctMenuOpen = false;
    },
    toggleIsAcctMenuOpen() {
      this.isAcctMenuOpen = !this.isAcctMenuOpen;
    },
    /**
     * For convenience, set current viewing data according to unique user ID.
     * @param {string} username 
     */
    setCurrentViewingUser(username: string) {
      const userFind = this.allUserAccountList.find(user => user.username === username);
      if (!userFind) return;
      this.currentViewingUser = userFind;
    },
    /**
     * We have this method because we want to handle create new modal case.
     */
    clearCurrentViewingUser() {
      this.currentViewingUser = {
        username: '',
        detail: {},
        name: '',
        is_admin: false,
        is_sso: false,
        isDeleteHovered: false,
        isRowHovered: false,
        isEditHovered: false,
        isDetailHovered: false,
      };
    },
    /**
     * Get all user accounts
     */
    async getAllUserAccounts() {
      const apiGetUserList = `/api/users`;
      try {
        const response = await apiClient.get(apiGetUserList);
        const customizedResponseData = await this.customizeAllUserList(response.data);
        this.allUserAccountList = await this.moveCurrentLoginUserToFirstRow(customizedResponseData);
      } catch (error) {
        apiError(error, 'Failed to get all users.');
      }
    },
    /**
     * Add some customization. For example, add isHovered field
     */
    async customizeAllUserList(rawResponseData: User[]): Promise<User[]> {
      const loginStore = useLoginStore();
      await loginStore.getUserData();
      const loginUserData:User = loginStore.userData;
      return rawResponseData.map(user => ({
        ...user, // Preserve fields from the backend response
        isCurrentLoggedIn: loginUserData.username === user.username,
        isDeleteHovered: false, // Additional fields for frontend display state
        isRowHovered: false,
        isEditHovered: false,
      }));
    },
    /**
     * Current logged in user should be placed at the first row on list
     */
    async moveCurrentLoginUserToFirstRow(fetchedUserList: User[]): Promise<User[]> {
      const loginStore = useLoginStore();
      const loginUserData:User = loginStore.userData;
      const foundLoginUserIndex = fetchedUserList.findIndex(user => user.username === loginUserData.username);
      if (foundLoginUserIndex !== -1) {
        fetchedUserList.unshift(fetchedUserList.splice(foundLoginUserIndex, 1)[0]);
      }
      return fetchedUserList;
    },
    /**
     * Create new user in database.
     * @param {object} userToCreate
     * userToCreate {
     * "username": "string",
     * "password": "string",
     * "name": "string",
     * "is_admin": boolean,
     * "is_active": boolean,
     * }
     */
    async createNewAccount(userToCreate: User) {
      const apiCreateAccount = `/api/users`;

      try {
        const response = await apiClient.post(apiCreateAccount, userToCreate);
        if (response.status === 200) {
          this.isOneAccountJustCreate = true;
          this.justCreateUsername = userToCreate.username;
          setTimeout(() => this.resetJustCreateFlag(), JUST_CREATE_ACCOUNT_HOT_DURATION_MINS * 1000 * 60);
        }
      } catch (error) {
        apiError(error, 'Failed to add a new account.');
      }
    },
    /**
     * Delete an account from database.
     * @param {string} userToDelete this value is unique in database.
     * @returns the result of whether the deletion is success or not.
     */
    async deleteAccount(userToDelete: string): Promise<boolean> {
      const apiDelete = `/api/users/${encodeURIComponent(userToDelete)}`;

      try {
        const response = await apiClient.delete(apiDelete);
        return response.status === 200;
      } catch (error) {
        apiError(error, 'Failed to delete the account.');
        return false;
      }
    },
    /**
     * Edit an account.
     * @param {string} userToEdit this value is unique in database.
     * @param {object} editDetail
     */
    async editAccount(userToEdit: string, editDetail: EditDetail): Promise<boolean> {
      const apiEdit = `/api/users/${encodeURIComponent(userToEdit)}`;

      try {
        const response = await apiClient.put(apiEdit, {
          username: editDetail.newUsername ?? editDetail.username,
          password: editDetail.password,
          name: editDetail.name,
          is_active: editDetail.is_active,
        });
        return response.status === 200;
      } catch (error) {
        apiError(error, 'Failed to edit the account.');
        return false;
      }
    },
    async editAccountName(userToEdit: string, newName: string): Promise<boolean> {
      const apiEdit = `/api/users/${encodeURIComponent(userToEdit)}`;

      try {
        const response = await apiClient.put(apiEdit, {
          username: userToEdit,
          name: newName,
          is_active: this.currentViewingUser.is_active,
          is_admin: this.currentViewingUser.is_admin,
        });
        return response.status === 200;
      } catch (error) {
        apiError(error, 'Failed to edit name of account.');
        return false;
      }
    },
    async editAccountPwd(userToEdit: string, newPwd: string): Promise<boolean> {
      const apiEdit = `/api/users/${encodeURIComponent(userToEdit)}`;

      try {
        const response = await apiClient.put(apiEdit, {
          username: userToEdit,
          name: this.currentViewingUser.name,
          password: newPwd,
          is_active: this.currentViewingUser.is_active,
          is_admin: this.currentViewingUser.is_admin,
        });
        return response.status === 200;
      } catch (error) {
        apiError(error, 'Failed to edit password of account.');
        return false;
      }
    },
    /** Add a role to the user in database.
     * @param {string} usernameToEdit
     * @param {string} roleCode
     */
    async addRoleToUser(usernameToEdit: string, roleCode: string): Promise<boolean> {
      const apiAddRole = `/api/users/${encodeURIComponent(usernameToEdit)}/roles/${encodeURIComponent(roleCode)}`;

      try {
        const response = await apiClient.put(apiAddRole);
        return response.status === 200;
      } catch (error) {
        apiError(error, 'Failed to add role to the account.');
        return false;
      }
    },
    /** Delete a role from the user in database.
     * @param {string} usernameToEdit
     * @param {string} roleCode
     */
    async deleteRoleToUser(usernameToEdit: string, roleCode: string): Promise<boolean> {
      const apiDeleteRole = `/api/users/${encodeURIComponent(usernameToEdit)}/roles/${encodeURIComponent(roleCode)}`;

      try {
        const response = await apiClient.delete(apiDeleteRole);
        return response.status === 200;
      } catch (error) {
        apiError(error, 'Failed to delete a role from the account.');
        return false;
      }
    },
    /**
     * Get user detail by unique username.
     * @param {string} uniqueUsername
    */
    async getUserDetail(uniqueUsername: string): Promise<boolean> {
      const apiUserDetail = `/api/users/${encodeURIComponent(uniqueUsername)}`;
      try {
        const response = await apiClient.get(apiUserDetail);
        this.currentViewingUser = response.data;
        this.currentViewingUser.is_admin = response.data.roles?.some(role => role.code === 'admin') ?? false;
        return response.status === 200;
      } catch {
        // No need to show an error, because an error here means the account is unique
        return false;
      }
    },
    /**
     * According to mouseover or mouseleave status, change the bool value.
     * @param {string} username 
     * @param {boolean} isDeleteHovered 
     */
    changeIsDeleteHoveredByUser(username: string, isDeleteHovered: boolean) {
      const userToChange = this.allUserAccountList.find(user => user.username === username);
      if (userToChange) {
        userToChange.isDeleteHovered = isDeleteHovered;
      }
    },
    /**
     * According to mouseover or mouseleave status, change the bool value.
     * @param {string} username 
     * @param {boolean} isRowHovered 
     */
    changeIsRowHoveredByUser(username: string, isRowHovered: boolean) {
      const userToChange = this.allUserAccountList.find(user => user.username === username);
      if (userToChange) {
        userToChange.isRowHovered = isRowHovered;
      }
    },
    /**
     * According to mouseover or mouseleave status, change the bool value.
     * @param {string} username 
     * @param {boolean} isEditHovered 
     */
    changeIsEditHoveredByUser(username: string, isEditHovered: boolean) {
      const userToChange = this.allUserAccountList.find(user => user.username === username);
      if (userToChange) {
        userToChange.isEditHovered = isEditHovered;
      }
    },
    /**
     * According to mouseover or mouseleave status, change the bool value.
     * @param {string} username
     * @param {boolean} isDetailHovered
     */
    changeIsDetailHoveredByUser(username: string, isDetailHovered: boolean) {
      const userToChange = this.allUserAccountList.find(user => user.username === username);
      if (userToChange) {
        userToChange.isDetailHovered = isDetailHovered;
      }
    },
    /**
     * Reset isOneAccountJustCreate to false, causing the badge to disappear.
     */
    resetJustCreateFlag() {
      this.isOneAccountJustCreate = false;
    },
    /** Set the value of shouldUpdateList variable.
     * @param {boolean} shouldUpdateBoolean
     */
    setShouldUpdateList(shouldUpdateBoolean: boolean) {
      this.shouldUpdateList = shouldUpdateBoolean;
    },
    /** Only update one single account in the pinia state.
     * @param {object} userDataToReplace
     */
    updateSingleAccountPiniaState(userDataToReplace: User) {
      const userIndex = this.allUserAccountList.findIndex(user => user.username === userDataToReplace.username);
      if (userIndex !== -1) {
        this.allUserAccountList[userIndex] = { ...this.allUserAccountList[userIndex], ...userDataToReplace };
      }
    }
  },
});
