import Foundation
import GoogleSignIn
import React

@objc(PortalGoogleSignIn)
class PortalGoogleSignIn: NSObject {

  private var configuration: GIDConfiguration?
  private var scopes: [String]?
  @objc var profileImageSize: UInt = 120

  // MARK: - Configure (old arch entry point)

  @objc func configure(
    _ options: NSDictionary,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    configureImpl(options, resolve: resolve, reject: reject)
  }

  // MARK: - Configure

  @objc func configureImpl(
    _ options: NSDictionary,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    let pathName = (options["googleServicePlistPath"] as? String) ?? "GoogleService-Info"
    let path = Bundle.main.path(forResource: pathName, ofType: "plist")

    let clientId: String? = {
      if let explicit = options["iosClientId"] as? String {
        return explicit
      }
      if let path = path,
         let plist = NSDictionary(contentsOfFile: path) {
        return plist["CLIENT_ID"] as? String
      }
      return nil
    }()

    guard let clientId = clientId else {
      let message = "PortalGoogleSignIn: failed to determine clientID - "
        + "GoogleService-Info.plist was not found and iosClientId was not provided. "
        + "To fix this error: if you have GoogleService-Info.plist file (usually downloaded "
        + "from firebase) place it into the project as seen in the iOS guide. Otherwise pass "
        + "iosClientId option to configure()"
      NSLog("%@", message)
      reject("CONFIGURE_ERROR", message, nil)
      return
    }

    if let size = options["profileImageSize"] as? NSNumber {
      profileImageSize = size.uintValue
    }

    configuration = GIDConfiguration(
      clientID: clientId,
      serverClientID: options["webClientId"] as? String,
      hostedDomain: options["hostedDomain"] as? String,
      openIDRealm: options["openIDRealm"] as? String
    )
    scopes = options["scopes"] as? [String]
    resolve(NSNull())
  }

  // MARK: - Sign In Silently

  @objc func signInSilently(
    _ resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    GIDSignIn.sharedInstance.restorePreviousSignIn { [weak self] result, error in
      if let error = error {
        self?.rejectWithSigninError(error, reject: reject)
      } else {
        resolve(self?.createUserDictionary(result))
      }
    }
  }

  // MARK: - Sign In (old arch entry point)

  @objc func signIn(
    _ options: NSDictionary,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    signInImpl(options, resolve: resolve, reject: reject)
  }

  // MARK: - Sign In

  @objc func signInImpl(
    _ options: NSDictionary,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let presentingViewController = RCTPresentedViewController() else {
      reject("SIGN_IN_ERROR", "No presenting view controller", nil)
      return
    }
    let hint = options["loginHint"] as? String

    guard let config = self.configuration else {
      reject("SIGN_IN_ERROR", "Google Sign-In not configured. Call configure() first.", nil)
      return
    }
    GIDSignIn.sharedInstance.configuration = config
    
    GIDSignIn.sharedInstance.signIn(
      withPresenting: presentingViewController,
      hint: hint,
      additionalScopes: scopes
    ) { [weak self] result, error in
      if let error = error {
        self?.rejectWithSigninError(error, reject: reject)
      } else {
        resolve(self?.createUserDictionary(result?.user, serverAuthCode: result?.serverAuthCode))
      }
    }
  }

  // MARK: - Add Scopes (old arch entry point)

  @objc func addScopes(
    _ options: NSDictionary,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    addScopesImpl(options, resolve: resolve, reject: reject)
  }

  // MARK: - Add Scopes

  @objc func addScopesImpl(
    _ options: NSDictionary,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    let scopes = options["scopes"] as? [String] ?? []
    guard let presentingViewController = RCTPresentedViewController() else {
      reject("ADD_SCOPES_ERROR", "No presenting view controller", nil)
      return
    }

    guard let config = self.configuration else {
      reject("ADD_SCOPES_ERROR", "Google Sign-In not configured. Call configure() first.", nil)
      return
    }
    GIDSignIn.sharedInstance.configuration = config

    // GoogleSignIn 7.x removed addScopes() - use signIn with additionalScopes instead
    GIDSignIn.sharedInstance.signIn(
      withPresenting: presentingViewController,
      hint: nil,
      additionalScopes: scopes
    ) { [weak self] result, error in
      if let error = error {
        self?.rejectWithSigninError(error, reject: reject)
      } else {
        resolve(self?.createUserDictionary(result?.user, serverAuthCode: result?.serverAuthCode))
      }
    }
  }

  // MARK: - Sign Out

  @objc func signOut(
    _ resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    GIDSignIn.sharedInstance.signOut()
    resolve(NSNull())
  }

  // MARK: - Revoke Access

  @objc func revokeAccess(
    _ resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    GIDSignIn.sharedInstance.disconnect { error in
      if let error = error {
        reject("REVOKE_ERROR", error.localizedDescription, error)
      } else {
        resolve(NSNull())
      }
    }
  }

  // MARK: - Is Signed In

  @objc func isSignedIn(
    _ resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    resolve(GIDSignIn.sharedInstance.hasPreviousSignIn())
  }

  // MARK: - Get Current User

  @objc func getCurrentUser(
    _ resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    let currentUser = GIDSignIn.sharedInstance.currentUser
    resolve(createUserDictionary(currentUser))
  }

  // MARK: - Get Tokens

  @objc func getTokens(
    _ resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) {
    guard let currentUser = GIDSignIn.sharedInstance.currentUser else {
      reject("GET_TOKENS_ERROR", "getTokens requires a user to be signed in", nil)
      return
    }

    // GoogleSignIn 7.x: use refreshTokensIfNeeded instead of authentication.do(freshTokens:)
    currentUser.refreshTokensIfNeeded { user, error in
      if let error = error {
        reject("GET_TOKENS_ERROR", error.localizedDescription, error)
      } else if let user = user {
        let result: [String: Any] = [
          "idToken": user.idToken?.tokenString ?? NSNull(),
          "accessToken": user.accessToken.tokenString,
        ]
        resolve(result)
      } else {
        reject("GET_TOKENS_ERROR", "user was null", nil)
      }
    }
  }

  // MARK: - Android-only stubs

  @objc func clearCachedAccessToken(
    _ tokenString: NSString,
    resolve: @escaping RCTPromiseResolveBlock,
    reject _: @escaping RCTPromiseRejectBlock
  ) {
    resolve(NSNull())
  }

  @objc func playServicesAvailable(
    _ showPlayServicesUpdateDialog: Bool,
    resolve: @escaping RCTPromiseResolveBlock,
    reject _: @escaping RCTPromiseRejectBlock
  ) {
    resolve(false)
  }

  // MARK: - URL Handling

  @objc static func application(
    _ app: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey: Any] = [:]
  ) -> Bool {
    return GIDSignIn.sharedInstance.handle(url)
  }

  // MARK: - Helpers

  func createUserDictionary(_ user: GIDGoogleUser?, serverAuthCode: String? = nil) -> NSDictionary? {
    guard let user = user else { return nil }

    var imageURL: URL? = nil
    if user.profile?.hasImage == true {
      imageURL = user.profile?.imageURL(withDimension: profileImageSize)
    }

    let userInfo: [String: Any] = [
      "id": user.userID ?? NSNull(),
      "name": user.profile?.name ?? NSNull(),
      "givenName": user.profile?.givenName ?? NSNull(),
      "familyName": user.profile?.familyName ?? NSNull(),
      "photo": imageURL?.absoluteString ?? NSNull(),
      "email": user.profile?.email ?? NSNull(),
    ]

    let params: [String: Any] = [
      "user": userInfo,
      "idToken": user.idToken?.tokenString ?? NSNull(),
      "serverAuthCode": serverAuthCode ?? NSNull(),
      "scopes": user.grantedScopes ?? NSNull(),
    ]

    return params as NSDictionary
  }

  private func rejectWithSigninError(_ error: Error, reject: RCTPromiseRejectBlock) {
    let nsError = error as NSError
    var errorMessage = "Unknown error when signing in."

    switch nsError.code {
    case GIDSignInError.unknown.rawValue:
      errorMessage = "Unknown error when signing in."
    case GIDSignInError.keychain.rawValue:
      errorMessage = "A problem reading or writing to the application keychain."
    case GIDSignInError.hasNoAuthInKeychain.rawValue:
      errorMessage = "The user has never signed in before with the given scopes, or they have since signed out."
    case GIDSignInError.canceled.rawValue:
      errorMessage = "The user canceled the sign in request."
    case GIDSignInError.EMM.rawValue:
      errorMessage = "An Enterprise Mobility Management related error has occurred."
    default:
      break
    }

    let errorCode = "\(nsError.code)"
    let message = "PortalGoogleSignInError: \(errorMessage), \(nsError.description)"
    reject(errorCode, message, error)
  }
}
