// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

#if os(iOS)

  /// The subclass of base class `MultiFactorAssertion`, used to assert ownership of a phone
  /// second factor.
  ///
  /// This class is available on iOS only.
  @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  @objc(FIRMultiFactorResolver)
  open class MultiFactorResolver: NSObject {
    /// The opaque session identifier for the current sign-in flow.
    @objc public let session: MultiFactorSession

    /// The list of hints for the second factors needed to complete the sign-in for the current
    /// session.
    @objc public let hints: [MultiFactorInfo]

    /// The Auth reference for the current `MultiResolver`.
    @objc public let auth: Auth

    /// A helper function to help users complete sign in with a second factor using a
    /// - Parameter assertion: The assertion confirming the user successfully
    ///  completed the second factor challenge.
    /// - Parameter completion: The block invoked when the request is complete, or fails.
    @objc(resolveSignInWithAssertion:completion:)
    open func resolveSignIn(with assertion: MultiFactorAssertion,
                            completion: ((AuthDataResult?, Error?) -> Void)? = nil) {
      var finalizedMFARequestInfo: AuthProto?
      if let totpAssertion = assertion as? TOTPMultiFactorAssertion {
        switch totpAssertion.secretOrID {
        case .secret: fatalError("Missing enrollmentID in totpAssertion")
        case let .enrollmentID(enrollmentID):
          finalizedMFARequestInfo = AuthProtoFinalizeMFATOTPSignInRequestInfo(
            mfaEnrollmentID: enrollmentID,
            verificationCode: totpAssertion.oneTimePassword
          )
        }
      } else {
        let phoneAssertion = assertion as? PhoneMultiFactorAssertion
        guard let credential = phoneAssertion?.authCredential else {
          fatalError("Internal Error: Missing credential")
        }
        switch credential.credentialKind {
        case .phoneNumber: fatalError("Internal Error: Missing verificationCode")
        case let .verification(verificationID, code):
          finalizedMFARequestInfo =
            AuthProtoFinalizeMFAPhoneRequestInfo(
              sessionInfo: verificationID,
              verificationCode: code
            )
        }
      }
      let request = FinalizeMFASignInRequest(
        mfaPendingCredential: mfaPendingCredential,
        verificationInfo: finalizedMFARequestInfo,
        requestConfiguration: auth.requestConfiguration
      )
      Task {
        do {
          let response = try await self.auth.backend.call(with: request)
          let user = try await self.auth.completeSignIn(withAccessToken: response.idToken,
                                                        accessTokenExpirationDate: nil,
                                                        refreshToken: response.refreshToken,
                                                        anonymous: false)
          let result = AuthDataResult(withUser: user, additionalUserInfo: nil)
          let decoratedCallback = self.auth
            .signInFlowAuthDataResultCallback(byDecorating: completion)
          decoratedCallback(.success(result))
        } catch {
          if let completion {
            completion(nil, error)
          }
        }
      }
    }

    /// A helper function to help users complete sign in with a second factor using a
    /// - Parameter assertion: The assertion confirming the user successfully
    ///  completed the second factor challenge.
    @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
    open func resolveSignIn(with assertion: MultiFactorAssertion) async throws -> AuthDataResult {
      return try await withCheckedThrowingContinuation { continuation in
        self.resolveSignIn(with: assertion) { result, error in
          if let result {
            continuation.resume(returning: result)
          } else {
            continuation.resume(throwing: error!)
          }
        }
      }
    }

    let mfaPendingCredential: String?

    init(with mfaPendingCredential: String?, hints: [MultiFactorInfo], auth: Auth) {
      self.mfaPendingCredential = mfaPendingCredential
      self.hints = hints
      self.auth = auth
      session = MultiFactorSession(mfaCredential: mfaPendingCredential)
    }
  }

#endif
