import UIKit
import Models
import Combine
import XXClient
import Defaults
import NetworkMonitor
import XXMessengerClient
import DependencyInjection

import CloudFiles
import CloudFilesSFTP
import CloudFilesDrive
import CloudFilesICloud
import CloudFilesDropbox

import KeychainAccess

public enum BackupProvider: Equatable, Codable {
  case sftp
  case drive
  case icloud
  case dropbox
}

public final class BackupService {
  @Dependency var messenger: Messenger
  @Dependency var networkManager: NetworkMonitoring

  @KeyObject(.email, defaultValue: nil) var email: String?
  @KeyObject(.phone, defaultValue: nil) var phone: String?
  @KeyObject(.username, defaultValue: nil) var username: String?
  @KeyObject(.backupSettings, defaultValue: nil) var storedSettings: Data?

  public var settingsPublisher: AnyPublisher<BackupSettings, Never> {
    settings.handleEvents(receiveSubscription: { [weak self] _ in
      guard let self = self else { return }
      self.refreshConnections()
      self.refreshBackups()
    }).eraseToAnyPublisher()
  }

  private var connType: ConnectionType = .wifi
  private var cancellables = Set<AnyCancellable>()
  private lazy var settings = CurrentValueSubject<BackupSettings, Never>(.init(fromData: storedSettings))

  public init() {
    settings
      .dropFirst()
      .removeDuplicates()
      .sink { [unowned self] in storedSettings = $0.toData() }
      .store(in: &cancellables)

    networkManager.connType
      .receive(on: DispatchQueue.main)
      .sink { [unowned self] in connType = $0 }
      .store(in: &cancellables)
  }

  public func setupSFTP(host: String, username: String, password: String) {
    managers[.sftp] = .sftp(
      host: host,
      username: username,
      password: password,
      fileName: "backup.xxm"
    )
    refreshBackups()
    refreshConnections()
  }
}

extension BackupService {
  public func stopBackups() {
    if messenger.isBackupRunning() == true {
      try! messenger.stopBackup()
    }
  }

  public func initializeBackup(passphrase: String) {
    try! messenger.startBackup(
      password: passphrase,
      params: .init(
        username: username!,
        email: email,
        phone: phone
      )
    )
  }

  public func performBackupIfAutomaticIsEnabled() {
    guard settings.value.automaticBackups == true else { return }
    performBackup()
  }

  public func performBackup() {
    guard let directoryUrl = try? FileManager.default.url(
      for: .applicationSupportDirectory,
      in: .userDomainMask,
      appropriateFor: nil,
      create: true
    ) else { fatalError("Couldn't generate the URL to persist the backup") }

    let fileUrl = directoryUrl
      .appendingPathComponent("backup")
      .appendingPathExtension("xxm")

    guard let data = try? Data(contentsOf: fileUrl) else { return }
    performBackup(data: data)
  }

  public func updateBackup(data: Data) {
    guard let directoryUrl = try? FileManager.default.url(
      for: .applicationSupportDirectory,
      in: .userDomainMask,
      appropriateFor: nil,
      create: true
    ) else { fatalError("Couldn't generate the URL to persist the backup") }

    let fileUrl = directoryUrl
      .appendingPathComponent("backup")
      .appendingPathExtension("xxm")

    do {
      try data.write(to: fileUrl)
    } catch {
      fatalError("Couldn't write backup to fileurl")
    }

    let isWifiOnly = settings.value.wifiOnlyBackup
    let isAutomaticEnabled = settings.value.automaticBackups
    let hasEnabledService = settings.value.enabledService != nil

    if isWifiOnly {
      guard connType == .wifi else { return }
    } else {
      guard connType != .unknown else { return }
    }

    if isAutomaticEnabled && hasEnabledService {
      performBackup()
    }

    refreshBackups()
  }

  public func setBackupOnlyOnWifi(_ enabled: Bool) {
    settings.value.wifiOnlyBackup = enabled
  }

  public func setBackupAutomatically(_ enabled: Bool) {
    settings.value.automaticBackups = enabled

    guard enabled else { return }
    performBackup()
  }

  public func toggle(service: BackupProvider, enabling: Bool) {
    settings.value.enabledService = enabling ? service : nil
  }

  public func authorize(
    service: BackupProvider,
    presenting screen: UIViewController
  ) {
    do {
      try managers[service]?.link(screen) { [weak self] in
        guard let self else { return }
        switch $0 {
        case .success:
          self.refreshConnections()
          self.refreshBackups()
        case .failure(let error):
          print(error.localizedDescription)
        }
      }
    } catch {
      print(error.localizedDescription)
    }
  }
}

extension BackupService {
  private func refreshConnections() {
    managers.forEach { provider, manager in
      if manager.isLinked() && !settings.value.connectedServices.contains(provider) {
        settings.value.connectedServices.insert(provider)
      } else if !manager.isLinked() && settings.value.connectedServices.contains(provider) {
        settings.value.connectedServices.remove(provider)
      }
    }
  }

  private func refreshBackups() {
    managers.forEach { provider, manager in
      if manager.isLinked() {
        do {
          try manager.fetch { [weak self] in
            guard let self else { return }

            switch $0 {
            case .success(let metadata):
              self.settings.value.backups[provider] = metadata
            case .failure(let error):
              print(error.localizedDescription)
            }
          }
        } catch {
          print(error.localizedDescription)
        }
      }
    }
  }

  private func performBackup(data: Data) {
    guard let enabledService = settings.value.enabledService else {
      fatalError("Trying to backup but nothing is enabled")
    }

    let url = URL(fileURLWithPath: NSTemporaryDirectory())
      .appendingPathComponent(UUID().uuidString)

    do {
      try data.write(to: url, options: .atomic)
    } catch {
      print(">>> Couldn't write to temp: \(error.localizedDescription)")
      return
    }

    if enabledService == .sftp {
      let keychain = Keychain(service: "SFTP-XXM")
      guard let host = try? keychain.get("host"),
            let password = try? keychain.get("pwd"),
            let username = try? keychain.get("username") else {
        fatalError("Tried to perform an sftp backup but its not configured")
      }

      managers[.sftp] = .sftp(
        host: host,
        username: username,
        password: password,
        fileName: "backup.xxm"
      )
    }

    if let manager = managers[enabledService] {
      do {
        try manager.upload(data) { [weak self] in
          guard let self else { return }

          switch $0 {
          case .success(let metadata):
            self.settings.value.backups[enabledService] = .init(
              size: metadata.size,
              lastModified: metadata.lastModified
            )
          case .failure(let error):
            print(error.localizedDescription)
          }
        }
      } catch {
        print(error.localizedDescription)
      }
    }
  }
}