import {useRef} from "react"
import firebase from "firebase/compat/app"
import "firebase/compat/auth"
import "firebase/compat/firestore"
import "firebase/compat/messaging"
import {useCommon} from "../hooks"

export type DocumentData = firebase.firestore.DocumentData
export type DocumentSnapshot = firebase.firestore.DocumentSnapshot
export type QueryDocumentSnapshot = firebase.firestore.QueryDocumentSnapshot
export type QuerySnapshot = firebase.firestore.QuerySnapshot
export type CollectionRef = firebase.firestore.CollectionReference
export type DocumentRef = firebase.firestore.DocumentReference
export type SetOptions = firebase.firestore.SetOptions

export const useFirebase = (
  onVerified?: (user: firebase.auth.UserCredential, authToken: string) => void,
  onFailed?: (data?: string) => void
) => {
  const {isDocumentReady} = useCommon()
  const recaptchaVerifier = useRef<firebase.auth.RecaptchaVerifier>()
  const confirmationResult = useRef<firebase.auth.ConfirmationResult>()
  const firebaseConfig = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
    measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
  }

  const app = !firebase.apps.length ? firebase.initializeApp(firebaseConfig) : firebase.app()

  const auth = firebase.auth(app)
  const fireStore = firebase.firestore(app)
  let messaging: firebase.messaging.Messaging | null = null
  if (isDocumentReady()) messaging = firebase.messaging(app)
  const googleProvider = new firebase.auth.GoogleAuthProvider()
  const facebookProvider = new firebase.auth.FacebookAuthProvider()
  facebookProvider.addScope("email")

  /* Phone number Authentication */
  const onSignInSuccess = async (response: firebase.auth.UserCredential) => {
    recaptchaVerifier.current?.clear()
    const authToken = await response.user?.getIdToken()
    if (onVerified) onVerified(response, authToken || "")
  }

  /* Send OTP to the phone number */
  const sendOtp = (countryCode: string, mobile: string, onOtpSent: () => void, onFailed?: () => void) => {
    if (!recaptchaVerifier.current) {
      recaptchaVerifier.current = new firebase.auth.RecaptchaVerifier("auth-proceed-button", {
        size: "invisible",
      })
    }

    // recaptchaVerifier.current?.clear()

    auth
      .signInWithPhoneNumber(countryCode + mobile, recaptchaVerifier.current)
      .then(response => {
        confirmationResult.current = response
        console.log("OTP Sent Successfully >>>>>>>>>>>>>>")
        onOtpSent()
      })
      .catch(reason => {
        console.error("FIREBASE ERROR While sending OTP >>>>>>>>>>>>>>", reason, recaptchaVerifier.current)
        if (onFailed) onFailed()
      })
  }

  /* Verify the sent OTP to the phone number */
  const verifyOTP = (otp: string) => {
    if (confirmationResult.current) {
      confirmationResult.current
        .confirm(otp)
        .then(onSignInSuccess)
        .catch(reason => {
          console.error("FIREBASE ERROR OTP Verification error >>>>>>>>>>>>>>", reason)
          if (onFailed) onFailed("Invalid OTP Please try again")
        })
    }
  }

  const getAuthToken = async (): Promise<string> => (await auth?.currentUser?.getIdToken()) || ""

  /* Google Provider Sign-in */

  const googleSignInPopup = async () => {
    try {
      const response = await auth.signInWithPopup(googleProvider)
      if (response) onSignInSuccess(response)
    } catch (error) {
      console.error("Firebase Google AuthToken googleSignInDesktop >>>>>>>>>>>", error)
      if (onFailed) onFailed()
    }
  }

  const googleSignInRedirect = () => {
    //To sign in by redirecting to the sign-in page
    auth.signInWithRedirect(googleProvider)
  }

  const googleSignInCallback = async () => {
    try {
      const response = await auth.getRedirectResult()
      if (response) onSignInSuccess(response)
    } catch (error) {
      console.error("Caught error in googleSignInRedirectResult >>>>>>>>>>>", error)
      if (onFailed) onFailed()
    }
  }

  /* Verifying user email and password */
  const verifyEmailPassword = (email: string, password: string) => {
    auth
      .signInWithEmailAndPassword(email, password)
      .then(onSignInSuccess)
      .catch(reason => {
        console.error("FIREBASE ERROR WHILE SIGNING IN >>>>>>>>>>>>>>", reason)
        if (onFailed) onFailed(reason?.message)
      })
  }

  /* new user Sign up with email password */
  const signupEmailPassword = (email: string, password: string) => {
    auth
      .createUserWithEmailAndPassword(email, password)
      .then(onSignInSuccess)
      .catch(reason => {
        console.error("FIREBASE CREATING EMAIL LOGIN >>>>>>>>>>>>>>", reason)
        if (onFailed) onFailed(reason?.message)
      })
  }

  /* sending login link to the user email */
  const sendEmailVerificationLink = async (
    email: string,
    redirectUrl: string,
    onSuccess: () => void,
    onFailure: (value: string) => void
  ) => {
    try {
      await auth.sendSignInLinkToEmail(email, {
        url: `${redirectUrl}?email=${email}`,
        handleCodeInApp: true,
      })
      onSuccess()
    } catch (error) {
      onFailure((error as Record<string, string>)["message"])
    }
  }

  /*signing in using email link verification*/
  const verifyEmailLogin = (email: string, url: string) => {
    auth
      .signInWithEmailLink(email, url)
      .then(onSignInSuccess)
      .catch(error => {
        if (onFailed) onFailed(error?.message)
      })
  }

  /*email link verification*/
  const isEmailSigninLink = (url: string) => auth.isSignInWithEmailLink(url)

  /*facebook login method*/
  const facebookSignInPopup = async () => {
    try {
      const response = await auth.signInWithPopup(facebookProvider)
      if (response) onSignInSuccess(response)
    } catch (error) {
      console.error("Firebase Facebook AuthToken facebookSignInDesktop >>>>>>>>>>>", error)
      if (onFailed) onFailed()
    }
  }

  const facebookSignInRedirect = () => {
    //To sign in by redirecting to the sign-in page
    auth.signInWithRedirect(facebookProvider)
  }

  /*facebook sign in call back method*/
  const facebookSignInCallback = async () => {
    try {
      const response = await auth.getRedirectResult()
      if (response) onSignInSuccess(response)
    } catch (error) {
      console.error("Caught error in facebookSignInRedirectResult >>>>>>>>>>>", error)
      if (onFailed) onFailed()
    }
  }

  const getSWRegistration = async () => {
    let registration: ServiceWorkerRegistration | undefined = undefined
    try {
      if ("serviceWorker" in navigator) {
        const registrations = await navigator.serviceWorker.getRegistrations()
        registration = registrations.find(
          worker => worker.active && worker.active.scriptURL && worker.active.scriptURL.includes("service-worker.js")
        )
      }
    } catch (error) {
      console.error(">>>>>>>>>>>>>> Caught Error while registering service-worker.js: ", error)
    }
    return registration
  }

  const getFCMToken = async () => {
    let fcmToken: null | string = null
    try {
      const registration = await getSWRegistration()
      if (messaging && registration) {
        fcmToken = await messaging.getToken({
          serviceWorkerRegistration: registration,
          vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY,
        })
        console.log(">>>>>>>>>>>>>>>: FCM Token Fetched successfully")
      }
    } catch (error) {
      console.error("Caught error in FCM TOKEN >>>>>>>>>>>: ", error)
    }
    return fcmToken
  }

  const getCollectionRef = (collections: string[]) => {
    const noOfCollection = collections.length

    let collectionRef: CollectionRef | DocumentRef = fireStore.collection(collections[0])
    switch (noOfCollection) {
      case 2:
        collectionRef = collectionRef.doc(collections[1])
        break

      case 3:
        collectionRef = collectionRef.doc(collections[1]).collection(collections[2])
        break

      case 4:
        collectionRef = collectionRef.doc(collections[1]).collection(collections[2]).doc(collections[3])
        break

      case 5:
        collectionRef = collectionRef
          .doc(collections[1])
          .collection(collections[2])
          .doc(collections[3])
          .collection(collections[4])
        break

      case 6:
        collectionRef = collectionRef
          .doc(collections[1])
          .collection(collections[2])
          .doc(collections[3])
          .collection(collections[4])
          .doc(collections[5])
        break

      case 7:
        collectionRef = collectionRef
          .doc(collections[1])
          .collection(collections[2])
          .doc(collections[3])
          .collection(collections[4])
          .doc(collections[5])
          .collection(collections[6])
        break

      case 8:
        collectionRef = collectionRef
          .doc(collections[1])
          .collection(collections[2])
          .doc(collections[3])
          .collection(collections[4])
          .doc(collections[5])
          .collection(collections[6])
          .doc(collections[7])
        break

      case 9:
        collectionRef = collectionRef
          .doc(collections[1])
          .collection(collections[2])
          .doc(collections[3])
          .collection(collections[4])
          .doc(collections[5])
          .collection(collections[6])
          .doc(collections[7])
          .collection(collections[8])
        break

      default:
        break
    }

    return collectionRef
  }

  const addData = async <D>(collections: string[], data: D, options: SetOptions = {merge: true, mergeFields: []}) => {
    let response: D | null = null
    try {
      const {mergeFields, merge} = options
      const option = mergeFields?.length ? {mergeFields} : {merge}
      if (collections.length % 2 === 0)
        await (getCollectionRef(collections) as DocumentRef).set(data as Partial<DocumentData>, option)
      else await (getCollectionRef(collections) as CollectionRef).add(data as Partial<DocumentData>)
      response = data
    } catch (error) {
      console.error("ERROR While Adding data to firestore: >>>>>>>>>>>>>>>>", error)
    }

    return response
  }

  const updateData = async <D>(collections: string[], data: D) => {
    let response: D | null = null
    try {
      await (getCollectionRef(collections) as DocumentRef).update(data as Partial<DocumentData>)
      response = data
    } catch (error) {
      console.error("ERROR While Adding data to firestore: >>>>>>>>>>>>>>>>", error)
    }

    return response
  }

  const getCollection = async <R>(collections: string[] | string) => {
    let data: R[] = []
    try {
      const response = await (
        getCollectionRef(typeof collections === "string" ? [collections] : collections) as CollectionRef
      ).get()

      data = response.docs.map(item => item.data() as R)
    } catch (error) {
      console.error("ERROR While fetching firestore collections: >>>>>>>>>>>>>>>>", error)
    }

    return data
  }

  const getData = async <R>(collections: string[] | string) => {
    let data: R | null = null
    try {
      const response = await (
        getCollectionRef(typeof collections === "string" ? [collections] : collections) as DocumentRef
      ).get()

      data = response.data() as R
    } catch (error) {
      console.error("ERROR While fetching firestore collections: >>>>>>>>>>>>>>>>", error)
    }

    return data
  }

  const deleteData = async (collections: string[] | string) => {
    let isSuccess: boolean = false
    try {
      await (getCollectionRef(typeof collections === "string" ? [collections] : collections) as DocumentRef).delete()

      isSuccess = true
    } catch (error) {
      console.error("ERROR While fetching firestore collections: >>>>>>>>>>>>>>>>", error)
    }

    return isSuccess
  }

  const onSignOut = async () => await auth.signOut()

  return {
    verifyOTP,
    sendOtp,
    getAuthToken,
    onSignOut,
    messaging,
    fireStore,
    getData,
    updateData,
    getCollection,
    addData,
    deleteData,
    googleSignInPopup,
    googleSignInRedirect,
    googleSignInCallback,
    verifyEmailPassword,
    signupEmailPassword,
    sendEmailVerificationLink,
    verifyEmailLogin,
    isEmailSigninLink,
    facebookSignInPopup,
    facebookSignInRedirect,
    facebookSignInCallback,
    getFCMToken,
    firebaseConfig,
  }
}
