import { makeAutoObservable } from 'mobx';
import {
  updateDoc,
  collection,
  doc,
  getDoc,
  getDocs,
  getFirestore,
  setDoc,
  Firestore
} from 'firebase/firestore';
import { getErrorMessage } from '../utils/Error';
import { User } from '../authentication/User';
import { replaceUndefinedValuesWithNull } from '..';
import { Feedback } from './Feedback';

/**
 * ApphouseFirestore
 * A wrapper around firebase firestore to fetch, set, update and delete data from firestore
 * It assumes that initializeApp(firebaseConfig) has already been called
 */
export class ApphouseFirestore<T extends string> {
  db?: Firestore;
  user?: User;
  feedback?: Feedback;
  /**
   * If true, then no error message will be shown as an alert
   * @default true
   */
  silentError: boolean;

  constructor(user: User, feedback?: Feedback) {
    this.user = user;
    this.db = getFirestore();
    this.feedback = feedback;
    this.silentError = true;
    makeAutoObservable(this);
  }

  setDb(db: Firestore) {
    this.db = db;
  }

  /**
   * Set silent error to true to not show error message as an alert
   * @param silentError if true, then no error message will be shown as an alert
   */
  setSilentError(silentError: boolean) {
    this.silentError = silentError;
  }

  fetch = async (
    collectionName: string,
    docName?: string,
    orderByField = 'updatedAt'
  ) => {
    if (this.db === undefined) {
      return undefined;
    }
    console.log('fetch', collectionName, docName, orderByField);
    if (docName) {
      const docRef = doc(this.db, collectionName, docName);
      const docSnap = await getDoc(docRef);
      if (docSnap.exists()) {
        return docSnap.data();
      } else {
        // doc.data() will be undefined in this case
        console.log('No such document!');
        if (!this.silentError) {
          this.feedback?.setFeedback({
            message: `No such document! ${collectionName} ${docName}`,
            variant: 'error'
          });
        }

        return undefined;
      }
    }
    const c = collection(this.db, collectionName);
    return await getDocs(c);
  };

  setWithoutCredentials = (collectionName: T, dcmt: string, data: object) => {
    if (this.db === undefined) {
      return undefined;
    }
    try {
      console.log('db set', { collection, doc, data });
      const docRef = doc(this.db, collectionName, dcmt);
      return setDoc(docRef, replaceUndefinedValuesWithNull(data));
    } catch (error) {
      if (!this.silentError) {
        this.feedback?.setFeedback({
          message: `${collection} ${doc} ${JSON.stringify(error)}`,
          variant: 'error'
        });
      }
      return {
        error: `${collection} ${doc} ${getErrorMessage(error)}`
      };
    }
  };

  set = (collectionName: T, dcmt: string, data: object) => {
    const user = this.user;
    if (!user) {
      return undefined;
    }
    if (this.db === undefined) {
      return undefined;
    }

    try {
      console.log('db set', { collection, doc, data });

      const sanitizedData = replaceUndefinedValuesWithNull(data);
      console.log({ sanitizedData: sanitizedData });

      const docRef = doc(this.db, collectionName, dcmt);
      this.feedback?.setFeedback({
        message: `Updates done successfully`,
        variant: 'success'
      });
      return setDoc(docRef, {
        ...sanitizedData,
        createdBy: user.email,
        ownerUid: user.uid,
        lastUpdatedBy: user.email,
        updatedAt: new Date().getTime()
      });
    } catch (error) {
      if (!this.silentError) {
        this.feedback?.setFeedback({
          message: `${collection} ${doc} ${JSON.stringify(error)}`,
          variant: 'error'
        });
      }
      return {
        error: `${collection} ${doc} ${getErrorMessage(error)}`
      };
    }
  };

  update = (collectionName: T, dcmt: string, data: any) => {
    const user = this.user;
    if (!user) {
      return undefined;
    }
    if (this.db === undefined) {
      return undefined;
    }
    try {
      console.log('db update', { collection, doc, data });
      const docRef = doc(this.db, collectionName, dcmt);
      this.feedback?.setFeedback({
        message: `Updates done successfully`,
        variant: 'success'
      });
      return updateDoc(docRef, {
        ...replaceUndefinedValuesWithNull(data),
        lastUpdatedBy: user?.email,
        updatedAt: data?.updatedAt || new Date().getTime()
      });
    } catch (error) {
      if (!this.silentError) {
        this.feedback?.setFeedback({
          message: `${collection} ${doc} ${JSON.stringify(error)}`,
          variant: 'error'
        });
      }
      return {
        error: `${collection} ${doc} ${getErrorMessage(error)}`
      };
    }
  };
}
