// Copyright 2022 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 FirebaseCore
@testable import FirebaseFunctions
#if COCOAPODS
  import GTMSessionFetcher
#else
  import GTMSessionFetcherCore
#endif

import XCTest
import SharedTestUtilities

class FunctionsTests: XCTestCase {
  var functions: Functions?
  var functionsCustomDomain: Functions?
  let fetcherService = GTMSessionFetcherService()
  let appCheckFake = FIRAppCheckFake()

  override func setUp() {
    super.setUp()
    functions = Functions(
      projectID: "my-project",
      region: "my-region",
      customDomain: nil,
      auth: nil,
      messaging: nil,
      appCheck: appCheckFake,
      fetcherService: fetcherService
    )
    functionsCustomDomain = Functions(projectID: "my-project", region: "my-region",
                                      customDomain: "https://mydomain.com", auth: nil,
                                      messaging: nil, appCheck: nil,
                                      fetcherService: fetcherService)
  }

  override func tearDown() {
    functions = nil
    functionsCustomDomain = nil
    super.tearDown()
  }

  func testFunctionsInstanceIsStablePerApp() throws {
    let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
                                  gcmSenderID: "00000000000000000-00000000000-000000000")
    options.projectID = "myProjectID"
    FirebaseApp.configure(options: options)
    var functions1 = Functions.functions()
    var functions2 = Functions.functions(app: FirebaseApp.app()!)
    XCTAssertEqual(functions1, functions2)

    FirebaseApp.configure(name: "test", options: options)
    let app2 = try XCTUnwrap(FirebaseApp.app(name: "test"))
    functions2 = Functions.functions(app: app2, region: "us-central2")
    XCTAssertNotEqual(functions1, functions2)

    functions1 = Functions.functions(app: app2, region: "us-central2")
    XCTAssertEqual(functions1, functions2)

    functions1 = Functions.functions(customDomain: "test_domain")
    functions2 = Functions.functions(region: "us-central1")
    XCTAssertNotEqual(functions1, functions2)

    functions2 = Functions.functions(app: FirebaseApp.app()!, customDomain: "test_domain")
    XCTAssertEqual(functions1, functions2)
  }

  func testURLWithName() throws {
    let url = try XCTUnwrap(functions?.urlWithName("my-endpoint"))
    XCTAssertEqual(url, "https://my-region-my-project.cloudfunctions.net/my-endpoint")
  }

  func testRegionWithEmulator() throws {
    functionsCustomDomain?.useEmulator(withHost: "localhost", port: 5005)
    let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint"))
    XCTAssertEqual(url, "http://localhost:5005/my-project/my-region/my-endpoint")
  }

  func testRegionWithEmulatorWithScheme() throws {
    functionsCustomDomain?.useEmulator(withHost: "http://localhost", port: 5005)
    let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint"))
    XCTAssertEqual(url, "http://localhost:5005/my-project/my-region/my-endpoint")
  }

  func testCustomDomain() throws {
    let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint"))
    XCTAssertEqual(url, "https://mydomain.com/my-endpoint")
  }

  func testSetEmulatorSettings() throws {
    functions?.useEmulator(withHost: "localhost", port: 1000)
    XCTAssertEqual("http://localhost:1000", functions?.emulatorOrigin)
  }

  // MARK: - App Check integration

  func testCallFunctionWhenAppCheckIsInstalledAndFACTokenSuccess() {
    appCheckFake.tokenResult = FIRAppCheckTokenResultFake(token: "valid_token", error: nil)

    let networkError = NSError(
      domain: "testCallFunctionWhenAppCheckIsInstalled",
      code: -1,
      userInfo: nil
    )

    let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
    fetcherService.testBlock = { fetcherToTest, testResponse in
      let appCheckTokenHeader = fetcherToTest.request?
        .value(forHTTPHeaderField: "X-Firebase-AppCheck")
      XCTAssertEqual(appCheckTokenHeader, "valid_token")
      testResponse(nil, nil, networkError)
      httpRequestExpectation.fulfill()
    }

    let completionExpectation = expectation(description: "completionExpectation")
    functions?.callFunction(name: "fake_func", withObject: nil, timeout: 10) { result in
      switch result {
      case .success:
        XCTFail("Unexpected success from functions?.callFunction")
      case let .failure(error as NSError):
        XCTAssertEqual(error, networkError)
      }
      completionExpectation.fulfill()
    }
    waitForExpectations(timeout: 1.5)
  }

  func testCallFunctionWhenAppCheckIsNotInstalled() {
    let networkError = NSError(
      domain: "testCallFunctionWhenAppCheckIsInstalled",
      code: -1,
      userInfo: nil
    )

    let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
    fetcherService.testBlock = { fetcherToTest, testResponse in
      let appCheckTokenHeader = fetcherToTest.request?
        .value(forHTTPHeaderField: "X-Firebase-AppCheck")
      XCTAssertNil(appCheckTokenHeader)
      testResponse(nil, nil, networkError)
      httpRequestExpectation.fulfill()
    }

    let completionExpectation = expectation(description: "completionExpectation")
    functionsCustomDomain?.callFunction(name: "fake_func", withObject: nil, timeout: 10) { result in
      switch result {
      case .success:
        XCTFail("Unexpected success from functions?.callFunction")
      case let .failure(error as NSError):
        XCTAssertEqual(error, networkError)
      }
      completionExpectation.fulfill()
    }
    waitForExpectations(timeout: 1.5)
  }
}
