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

/// Extends ApplicationInfoProtocol to string-format a combined appDisplayVersion and
/// appBuildVersion
extension ApplicationInfoProtocol {
  var synthesizedVersion: String { return "\(appDisplayVersion) (\(appBuildVersion))" }
}

class RemoteSettings: SettingsProvider {
  private static let cacheDurationSecondsDefault: TimeInterval = 60 * 60
  private static let flagSessionsEnabled = "sessions_enabled"
  private static let flagSamplingRate = "sampling_rate"
  private static let flagSessionTimeout = "session_timeout_seconds"
  private static let flagCacheDuration = "cache_duration"
  private static let flagSessionsCache = "app_quality"
  private let appInfo: ApplicationInfoProtocol
  private let downloader: SettingsDownloadClient
  private var cache: SettingsCacheClient

  private var cacheDurationSeconds: TimeInterval {
    guard let duration = cache.cacheContent[RemoteSettings.flagCacheDuration] as? Double else {
      return RemoteSettings.cacheDurationSecondsDefault
    }
    return duration
  }

  private var sessionsCache: [String: Any] {
    return cache.cacheContent[RemoteSettings.flagSessionsCache] as? [String: Any] ?? [:]
  }

  init(appInfo: ApplicationInfoProtocol,
       downloader: SettingsDownloadClient,
       cache: SettingsCacheClient = SettingsCache()) {
    self.appInfo = appInfo
    self.cache = cache
    self.downloader = downloader
  }

  private func fetchAndCacheSettings(currentTime: Date) {
    // Only fetch if cache is expired, otherwise do nothing
    guard isCacheExpired(time: currentTime) else {
      Logger.logDebug("[Settings] Cache is not expired, no fetch will be made.")
      return
    }

    downloader.fetch { result in
      switch result {
      case let .success(dictionary):
        // Saves all newly fetched Settings to cache
        self.cache.cacheContent = dictionary
        // Saves a "cache-key" which carries TTL metadata about current cache
        self.cache.cacheKey = CacheKey(
          createdAt: currentTime,
          googleAppID: self.appInfo.appID,
          appVersion: self.appInfo.synthesizedVersion
        )
      case let .failure(error):
        Logger.logError("[Settings] Fetching newest settings failed with error: \(error)")
      }
    }
  }
}

typealias RemoteSettingsConfigurations = RemoteSettings
extension RemoteSettingsConfigurations {
  var sessionsEnabled: Bool? {
    return sessionsCache[RemoteSettings.flagSessionsEnabled] as? Bool
  }

  var samplingRate: Double? {
    return sessionsCache[RemoteSettings.flagSamplingRate] as? Double
  }

  var sessionTimeout: TimeInterval? {
    return sessionsCache[RemoteSettings.flagSessionTimeout] as? Double
  }
}

typealias RemoteSettingsProvider = RemoteSettings
extension RemoteSettingsConfigurations {
  func updateSettings(currentTime: Date) {
    fetchAndCacheSettings(currentTime: currentTime)
  }

  func updateSettings() {
    updateSettings(currentTime: Date())
  }

  func isSettingsStale() -> Bool {
    return isCacheExpired(time: Date())
  }

  private func isCacheExpired(time: Date) -> Bool {
    guard !cache.cacheContent.isEmpty else {
      cache.removeCache()
      return true
    }
    guard let cacheKey = cache.cacheKey else {
      Logger.logError("[Settings] Could not load settings cache key")
      cache.removeCache()
      return true
    }
    guard cacheKey.googleAppID == appInfo.appID else {
      Logger
        .logDebug("[Settings] Cache expired because Google App ID changed")
      cache.removeCache()
      return true
    }
    if time.timeIntervalSince(cacheKey.createdAt) > cacheDurationSeconds {
      Logger.logDebug("[Settings] Cache TTL expired")
      return true
    }
    if appInfo.synthesizedVersion != cacheKey.appVersion {
      Logger.logDebug("[Settings] Cache expired because app version changed")
      return true
    }
    return false
  }
}
