import Foundation
import GoogleSignIn
import FirebaseAuth
import FirebaseFirestore

/// An observable class for authenticating via Google.
final class GoogleSignInAuthenticator: ObservableObject {
  private var authViewModel: AuthenticationViewModel

  /// Creates an instance of this authenticator.
  /// - parameter authViewModel: The view model this authenticator will set logged in status on.
  init(authViewModel: AuthenticationViewModel) {
    self.authViewModel = authViewModel
  }

  /// Signs in the user based upon the selected account.'
  /// - note: Successful calls to this will set the `authViewModel`'s `state` property.
  @MainActor func signIn() {
    guard let rootViewController = UIApplication.shared.windows.first?.rootViewController else {
      print("There is no root view controller!")
      return
    }

    GIDSignIn.sharedInstance.signIn(
      withPresenting: rootViewController,
    ) { signInResult, error in
        guard let user = signInResult?.user,
              let idToken = user.idToken?.tokenString else {
        print("Error! \(String(describing: error))")
        return
      }
        
        let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: user.accessToken.tokenString)
        
        Auth.auth().signIn(with: credential) { result, error in
            guard let result else {
                print("Error! \(String(describing: error))")
                return
            }
            
            // Upsert user document to Firestore
            self.upsertUserDocument(user: result.user)

            self.authViewModel.state = .signedIn(result.user)
        }
    }
  }

  /// Signs out the current user.
  func signOut() {
      try! Auth.auth().signOut()
    authViewModel.state = .signedOut
  }
  
  /// Upserts a user document to Firestore.
  /// - parameter user: The Firebase user to save.
  private func upsertUserDocument(user: User) {
    let db = Firestore.firestore()
    let userRef = db.collection("user").document(user.uid)

    let userData: [String: Any] = [
      "displayName": user.displayName ?? "",
      "isArtist": false
    ]
    
    userRef.setData(userData, merge: true) { error in
      if let error = error {
        print("Error upserting user document: \(error.localizedDescription)")
      } else {
        print("User document successfully upserted for user: \(user.uid)")
      }
    }
  }

}

// MARK: Parse nonce from JWT ID Token

private extension GoogleSignInAuthenticator {
  func decodeNonce(fromJWT jwt: String) -> String? {
    let segments = jwt.components(separatedBy: ".")
    guard let parts = decodeJWTSegment(segments[1]),
          let nonce = parts["nonce"] as? String else {
      return nil
    }
    return nonce
  }

  func decodeJWTSegment(_ segment: String) -> [String: Any]? {
    guard let segmentData = base64UrlDecode(segment),
          let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []),
          let payload = segmentJSON as? [String: Any] else {
      return nil
    }
    return payload
  }

  func base64UrlDecode(_ value: String) -> Data? {
    var base64 = value
      .replacingOccurrences(of: "-", with: "+")
      .replacingOccurrences(of: "_", with: "/")

    let length = Double(base64.lengthOfBytes(using: String.Encoding.utf8))
    let requiredLength = 4 * ceil(length / 4.0)
    let paddingLength = requiredLength - length
    if paddingLength > 0 {
      let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
      base64 = base64 + padding
    }
    return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
  }
}
