/**
 * Copyright (c) 2023-present Pi Dash Software, Inc. and contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 * See the LICENSE file for details.
 */

import { AxiosError } from "axios";
import { set } from "lodash-es";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
// pi dash imports
import { UserService } from "@pi-dash/services";
import type { ActorDetail, IUser } from "@pi-dash/types";
// store types
import type { IProfileStore } from "@/store/profile.store";
import { ProfileStore } from "@/store/profile.store";
// store
import type { RootStore } from "@/store/root.store";

type TUserErrorStatus = {
  status: string;
  message: string;
};

export interface IUserStore {
  // observables
  isAuthenticated: boolean;
  isInitializing: boolean;
  error: TUserErrorStatus | undefined;
  data: IUser | undefined;
  // store observables
  profile: IProfileStore;
  // computed
  currentActor: ActorDetail;
  // actions
  fetchCurrentUser: () => Promise<IUser | undefined>;
  updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
  hydrate: (data: IUser | undefined) => void;
  reset: () => void;
  signOut: () => Promise<void>;
}

export class UserStore implements IUserStore {
  // observables
  isAuthenticated: boolean = false;
  isInitializing: boolean = true;
  error: TUserErrorStatus | undefined = undefined;
  data: IUser | undefined = undefined;
  // store observables
  profile: IProfileStore;
  // service
  userService: UserService;

  constructor(private store: RootStore) {
    // stores
    this.profile = new ProfileStore(store);
    // service
    this.userService = new UserService();
    // observables
    makeObservable(this, {
      // observables
      isAuthenticated: observable.ref,
      isInitializing: observable.ref,
      error: observable,
      // model observables
      data: observable,
      profile: observable,
      // computed
      currentActor: computed,
      // actions
      fetchCurrentUser: action,
      updateCurrentUser: action,
      reset: action,
      signOut: action,
    });
  }

  // computed
  get currentActor(): ActorDetail {
    return {
      id: this.data?.id,
      first_name: this.data?.first_name,
      last_name: this.data?.last_name,
      display_name: this.data?.display_name,
      avatar_url: this.data?.avatar_url || undefined,
      is_bot: false,
    };
  }

  // actions
  /**
   * @description fetches the current user
   * @returns {Promise<IUser>}
   */
  fetchCurrentUser = async (): Promise<IUser> => {
    try {
      runInAction(() => {
        if (this.data === undefined && !this.error) this.isInitializing = true;
        this.error = undefined;
      });
      const user = await this.userService.me();
      if (user && user?.id) {
        await this.profile.fetchUserProfile();
        runInAction(() => {
          this.data = user;
          this.isInitializing = false;
          this.isAuthenticated = true;
        });
      } else
        runInAction(() => {
          this.data = user;
          this.isInitializing = false;
          this.isAuthenticated = false;
        });
      return user;
    } catch (error) {
      runInAction(() => {
        this.isInitializing = false;
        this.isAuthenticated = false;
        this.error = {
          status: "user-fetch-error",
          message: "Failed to fetch current user",
        };
        if (error instanceof AxiosError && error.status === 401) {
          this.data = undefined;
        }
      });
      throw error;
    }
  };

  /**
   * @description updates the current user
   * @param data
   * @returns {Promise<IUser>}
   */
  updateCurrentUser = async (data: Partial<IUser>): Promise<IUser> => {
    const currentUserData = this.data;
    try {
      if (currentUserData) {
        Object.keys(data).forEach((key: string) => {
          const userKey: keyof IUser = key as keyof IUser;
          if (this.data) set(this.data, userKey, data[userKey]);
        });
      }
      const user = await this.userService.update(data);
      return user;
    } catch (error) {
      if (currentUserData) {
        Object.keys(currentUserData).forEach((key: string) => {
          const userKey: keyof IUser = key as keyof IUser;
          if (this.data) set(this.data, userKey, currentUserData[userKey]);
        });
      }
      runInAction(() => {
        this.error = {
          status: "user-update-error",
          message: "Failed to update current user",
        };
      });
      throw error;
    }
  };

  hydrate = (data: IUser | undefined): void => {
    if (!data) return;
    this.data = { ...this.data, ...data };
  };

  /**
   * @description resets the user store
   * @returns {void}
   */
  reset = (): void => {
    runInAction(() => {
      this.isAuthenticated = false;
      this.isInitializing = false;
      this.error = undefined;
      this.data = undefined;
      this.profile = new ProfileStore(this.store);
    });
  };

  /**
   * @description signs out the current user
   * @returns {Promise<void>}
   */
  signOut = async (): Promise<void> => {
    this.store.reset();
  };
}
