import Bodega
import Models
import Network
import SwiftUI

public actor TimelineCache {
  private func storageFor(_ client: String, _ filter: String) -> SQLiteStorageEngine {
    if filter == "Home" {
      SQLiteStorageEngine.default(appendingPath: "\(client)")
    } else {
      SQLiteStorageEngine.default(appendingPath: "\(client)/\(filter)")
    }
  }

  private let decoder = JSONDecoder()
  private let encoder = JSONEncoder()

  public init() {}
  
  public func cachedPostsCount(for client: String) async -> Int {
    do {
      let directory = FileManager.Directory.defaultStorageDirectory(appendingPath: client).url
      let content = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil)
      var total: Int = await storageFor(client, "Home").allKeys().count
      for storage in content {
        if !storage.lastPathComponent.hasSuffix("sqlite3") {
          total += await storageFor(client, storage.lastPathComponent).allKeys().count
        }
      }
      return total
    } catch {
      return 0
    }
  }
  
  public func clearCache(for client: String) async {
    let directory = FileManager.Directory.defaultStorageDirectory(appendingPath: client)
    try? FileManager.default.removeItem(at: directory.url)
  }

  public func clearCache(for client: String, filter: String) async {
    let engine = storageFor(client, filter)
    do {
      try await engine.removeAllData()
    } catch {}
  }

  func set(statuses: [Status], client: String, filter: String) async {
    guard !statuses.isEmpty else { return }
    let statuses = statuses.prefix(upTo: min(600, statuses.count - 1)).map { $0 }
    do {
      let engine = storageFor(client, filter)
      try await engine.removeAllData()
      let itemKeys = statuses.map { CacheKey($0[keyPath: \.id]) }
      let dataAndKeys = try zip(itemKeys, statuses)
        .map { try (key: $0, data: encoder.encode($1)) }
      try await engine.write(dataAndKeys)
    } catch {}
  }

  func getStatuses(for client: String, filter: String) async -> [Status]? {
    let engine = storageFor(client, filter)
    do {
      return try await engine
        .readAllData()
        .map { try decoder.decode(Status.self, from: $0) }
        .sorted(by: { $0.createdAt.asDate > $1.createdAt.asDate })
    } catch {
      return nil
    }
  }

  func setLatestSeenStatuses(_ statuses: [Status], for client: Client, filter: String) {
    let statuses = statuses.sorted(by: { $0.createdAt.asDate > $1.createdAt.asDate })
    if filter == "Home" {
      UserDefaults.standard.set(statuses.map{ $0.id }, forKey: "timeline-last-seen-\(client.id)")
    } else {
      UserDefaults.standard.set(statuses.map{ $0.id }, forKey: "timeline-last-seen-\(client.id)-\(filter)")
    }
  }

  func getLatestSeenStatus(for client: Client, filter: String) -> [String]? {
    if filter == "Home" {
      UserDefaults.standard.array(forKey: "timeline-last-seen-\(client.id)") as? [String]
    } else {
      UserDefaults.standard.array(forKey: "timeline-last-seen-\(client.id)-\(filter)") as? [String]
    }
  }
}

// Quiets down the warnings from this one. Bodega is nicely async so we don't
// want to just use `@preconcurrency`, but the CacheKey type is (incorrectly)
// not marked as `Sendable`---it's a value type containing two `String`
// properties.
extension Bodega.CacheKey: @unchecked Sendable {}
