import Foundation
import XCTestDynamicOverlay
import XXClient

public struct BackupStorage {
  public struct Backup: Equatable {
    public init(
      date: Date,
      data: Data
    ) {
      self.date = date
      self.data = data
    }

    public var date: Date
    public var data: Data
  }

  public typealias Observer = (Backup?) -> Void

  public var store: (Data) throws -> Void
  public var observe: (@escaping Observer) -> Cancellable
  public var remove: () throws -> Void
}

extension BackupStorage {
  public static func onDisk(
    now: @escaping () -> Date = Date.init,
    fileManager: MessengerFileManager = .live(),
    path: String = FileManager.default
      .urls(for: .applicationSupportDirectory, in: .userDomainMask)
      .first!
      .appendingPathComponent("backup.xxm")
      .path
  ) -> BackupStorage {
    var observers: [UUID: Observer] = [:]
    var backup: Backup?
    func notifyObservers() {
      observers.values.forEach { $0(backup) }
    }
    if let fileData = try? fileManager.loadFile(path),
       let fileDate = try? fileManager.modifiedTime(path) {
      backup = Backup(date: fileDate, data: fileData)
    }
    return BackupStorage(
      store: { data in
        let newBackup = Backup(
          date: now(),
          data: data
        )
        backup = newBackup
        notifyObservers()
        try fileManager.saveFile(path, newBackup.data)
      },
      observe: { observer in
        let id = UUID()
        observers[id] = observer
        defer { observers[id]?(backup) }
        return Cancellable {
          observers[id] = nil
        }
      },
      remove: {
        backup = nil
        notifyObservers()
        try fileManager.removeItem(path)
      }
    )
  }
}

extension BackupStorage {
  public static let unimplemented = BackupStorage(
    store: XCTUnimplemented("\(Self.self).store"),
    observe: XCTUnimplemented("\(Self.self).observe", placeholder: Cancellable {}),
    remove: XCTUnimplemented("\(Self.self).remove")
  )
}