import Authsignal
import Foundation
import React
import Security

@objc(AuthsignalPasskeyModule)
class AuthsignalPasskeyModule: NSObject {
  var authsignal: AuthsignalPasskey?
  
  @objc static func requiresMainQueueSetup() -> Bool {
    return true
  }
  
  @objc func initialize(
    _ tenantID: NSString,
    withBaseURL baseURL: NSString,
    withDeviceID deviceID: NSString?,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) -> Void {
    RequestMetadata.configure()
    self.authsignal = AuthsignalPasskey(tenantID: tenantID as String, baseURL: baseURL as String, deviceID: deviceID as String?)
    
    resolve(nil)
  }
  
  @objc func signUp(
    _ token: NSString?,
    withUsername username: NSString?,
    withDisplayName displayName: NSString?,
    withIgnorePasskeyAlreadyExistsError ignorePasskeyAlreadyExistsError: Bool,
    withSyncCredentials syncCredentials: Bool,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) -> Void {
    if (authsignal == nil) {
      resolve(nil)
      return
    }

    let tokenStr = token as String?
    let usernameStr = username as String?
    let displayNameStr = displayName as String?

    Task.init {
      let response = await authsignal!.signUp(
        token: tokenStr,
        username: usernameStr,
        displayName: displayNameStr,
        ignorePasskeyAlreadyExistsError: ignorePasskeyAlreadyExistsError,
        syncCredentials: syncCredentials
      )

      if (response.error != nil) {
        reject(response.errorCode ?? "unexpected_error", response.error, nil)
      } else {
        let signUpResponse: [String: String?] = [
          "token": response.data?.token,
        ]

        resolve(signUpResponse)
      }
    }
  }
  
  @objc func signIn(
    _ action: NSString?,
    withToken token: NSString?,
    withAutofill autofill: Bool,
    withPreferImmediatelyAvailableCredentials preferImmediatelyAvailableCredentials: Bool,
    withSyncCredentials syncCredentials: Bool,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) -> Void {
    if (authsignal == nil) {
      resolve(nil)
      return
    }

    let actionStr = action as String?
    let tokenStr = token as String?

    Task.init {
      let response = await authsignal!.signIn(
        token: tokenStr,
        action: actionStr,
        autofill: autofill,
        preferImmediatelyAvailableCredentials: preferImmediatelyAvailableCredentials,
        syncCredentials: syncCredentials
      )
      
      if (response.error != nil) {
        reject(response.errorCode ?? "unexpected_error", response.error, nil)
      } else if let data = response.data {
        let signInResponse: [String: Any?] = [
          "isVerified": data.isVerified,
          "token": data.token,
          "userId": data.userId,
          "userAuthenticatorId": data.userAuthenticatorId,
          "username": data.username,
          "displayName": data.displayName,
        ]

        resolve(signInResponse)
      } else {
        reject("unexpected_error", "No data returned", nil)
      }
    }
  }
  
  @objc func cancel() -> Void {
    authsignal?.cancel()
  }
  
  @objc func invalidate() -> Void {
    authsignal?.cancel()
  }

  @objc func shouldPromptToCreatePasskey(
    _ username: NSString?,
    resolve: @escaping RCTPromiseResolveBlock,
    reject: @escaping RCTPromiseRejectBlock
  ) -> Void {
    if (authsignal == nil) {
      resolve(false)
      return
    }

    let usernameStr = username as String?
    
    Task.init {
      let response = await authsignal!.shouldPromptToCreatePasskey(username: usernameStr)
      
      if (response.error != nil) {
        resolve(false)
      } else {
        resolve(response.data)
      }
    }
  }

  @objc func isAvailableOnDevice(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
    if (authsignal == nil) {
      resolve(false)
      return
    }
    
    Task.init {
      let response = await authsignal!.isAvailableOnDevice()
      
      if (response.error != nil) {
        resolve(false)
      } else {
        resolve(response.data)
      }
    }
  }
}
