// 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
import XCTest

@testable import FirebaseAuth

@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
class SignInWithGameCenterTests: RPCBaseTests {
  private let kEmailKey = "email"
  private let kTestEmail = "testgmail.com"
  private let kDisplayNameKey = "displayName"
  private let kTestDisplayName = "DisplayName"
  private let kPasswordKey = "password"
  private let kTestPassword = "Password"
  private let kReturnSecureTokenKey = "returnSecureToken"
  private let kExpectedAPIURL =
    "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signInWithGameCenter?key=APIKey"
  let kIDToken = "IDTOKEN"
  let kPlayerIDKey = "playerId"
  let kPlayerID = "PLAYERID"
  let kTeamPlayerIDKey = "teamPlayerId"
  let kTeamPlayerID = "TEAMPLAYERID"
  let kGamePlayerIDKey = "gamePlayerId"
  let kGamePlayerID = "GAMEPLAYERID"
  let kApproximateExpirationDate = "3600"
  let kPublicKeyURLKey = "publicKeyUrl"
  let kPublicKeyURL = "PUBLICKEYURL"
  let kSignatureKey = "signature"
  let kSignature = "AAAABBBBCCCC"
  let kSaltKey = "salt"
  let kSalt = "AAAA"
  let kTimestampKey = "timestamp"
  let kTimestamp = UInt64(12_345_678)
  let kAccessTokenKey = "idToken"
  let kAccessToken = "ACCESSTOKEN"

  /** @fn testSignInWithGameCenterRequestAnonymous
      @brief Tests the encoding of a sign up new user request when user is signed in anonymously.
   */
  func testRequestResponseEncoding() async throws {
    let kRefreshToken = "PUBLICKEYURL"
    let kLocalID = "LOCALID"
    let kDisplayNameKey = "displayName"
    let kDisplayName = "DISPLAYNAME"

    let signature = try XCTUnwrap(Data(base64Encoded: kSignature))
    let salt = try XCTUnwrap(Data(base64Encoded: kSalt))
    let request = try SignInWithGameCenterRequest(playerID: kPlayerID,
                                                  teamPlayerID: kTeamPlayerID,
                                                  gamePlayerID: kGamePlayerID,
                                                  publicKeyURL: XCTUnwrap(
                                                    URL(string: kPublicKeyURL)
                                                  ),
                                                  signature: signature,
                                                  salt: salt,
                                                  timestamp: kTimestamp,
                                                  displayName: kDisplayName,
                                                  requestConfiguration: makeRequestConfiguration())
    request.accessToken = kAccessToken
    try await checkRequest(
      request: request,
      expected: kExpectedAPIURL,
      key: kPlayerIDKey,
      value: kPlayerID
    )
    let requestDictionary = try XCTUnwrap(rpcIssuer.decodedRequest as? [String: AnyHashable])
    XCTAssertEqual(requestDictionary[kTeamPlayerIDKey], kTeamPlayerID)
    XCTAssertEqual(requestDictionary[kGamePlayerIDKey], kGamePlayerID)
    XCTAssertEqual(requestDictionary[kPublicKeyURLKey], kPublicKeyURL)
    XCTAssertEqual(requestDictionary[kSignatureKey], kSignature)
    XCTAssertEqual(requestDictionary[kSaltKey], kSalt)
    XCTAssertEqual(requestDictionary[kTimestampKey], kTimestamp)
    XCTAssertEqual(requestDictionary[kAccessTokenKey], kAccessToken)
    XCTAssertEqual(requestDictionary[kDisplayNameKey], kDisplayName)

    rpcIssuer.respondBlock = {
      try self.rpcIssuer.respond(withJSON: [
        "idToken": self.kIDToken,
        "refreshToken": kRefreshToken,
        "localId": kLocalID,
        "playerId": self.kPlayerID,
        "teamPlayerId": self.kTeamPlayerID,
        "gamePlayerId": self.kGamePlayerID,
        "expiresIn": self.kApproximateExpirationDate,
        "isNewUser": true,
        "displayName": kDisplayName,
      ])
    }
    let rpcResponse = try await authBackend.call(with: request)
    XCTAssertNotNil(rpcResponse)

    XCTAssertEqual(rpcResponse.idToken, kIDToken)
    XCTAssertEqual(rpcResponse.refreshToken, kRefreshToken)
    XCTAssertEqual(rpcResponse.localID, kLocalID)
    XCTAssertEqual(rpcResponse.playerID, kPlayerID)
    XCTAssertEqual(rpcResponse.teamPlayerID, kTeamPlayerID)
    XCTAssertEqual(rpcResponse.gamePlayerID, kGamePlayerID)
    XCTAssertEqual(rpcResponse.displayName, kDisplayName)
    XCTAssertTrue(rpcResponse.isNewUser)
  }

  #if !os(watchOS)
    /** @fn testGameCenterAuthCredentialCoding
        @brief Tests successful archiving and unarchiving of @c GameCenterAuthCredential.
     */
    func testGameCenterAuthCredentialCoding() throws {
      let credential = try makeGameCenterCredential()
      XCTAssertTrue(GameCenterAuthCredential.supportsSecureCoding)
      let data = try NSKeyedArchiver.archivedData(
        withRootObject: credential,
        requiringSecureCoding: true
      )
      let unarchivedCredential = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject(
        ofClasses: [NSURL.self, GameCenterAuthCredential.self], from: data
      ) as? GameCenterAuthCredential)
      XCTAssertEqual(unarchivedCredential.playerID, kPlayerID)
      XCTAssertEqual(unarchivedCredential.teamPlayerID, kTeamPlayerID)
      XCTAssertEqual(unarchivedCredential.gamePlayerID, kGamePlayerID)
      XCTAssertEqual(unarchivedCredential.publicKeyURL, URL(string: kPublicKeyURL))
      XCTAssertEqual(try String(data: XCTUnwrap(unarchivedCredential.signature),
                                encoding: .utf8), kSignature)
      XCTAssertEqual(try String(data: XCTUnwrap(unarchivedCredential.salt), encoding: .utf8), kSalt)
      XCTAssertEqual(unarchivedCredential.timestamp, kTimestamp)
      XCTAssertEqual(unarchivedCredential.displayName, kDisplayName)
    }

    private func makeGameCenterCredential() throws -> GameCenterAuthCredential {
      let signature = try XCTUnwrap(kSignature.data(using: .utf8))
      let salt = try XCTUnwrap(kSalt.data(using: .utf8))
      return try GameCenterAuthCredential(withPlayerID: kPlayerID,
                                          teamPlayerID: kTeamPlayerID,
                                          gamePlayerID: kGamePlayerID,
                                          publicKeyURL: XCTUnwrap(
                                            URL(string: kPublicKeyURL)
                                          ),
                                          signature: signature,
                                          salt: salt,
                                          timestamp: kTimestamp,
                                          displayName: kDisplayName)
    }
  #endif
}
