import Photos

final class AssetCollectionRepository {
  static let shared = AssetCollectionRepository()
  private init() {}

  func getAll() -> [PHAssetCollection] {
    var collections: [PHAssetCollection] = []
    let pHFetchResult = PHCollectionList.fetchTopLevelUserCollections(with: nil)

    pHFetchResult.enumerateObjects { collection, _, _ in
      if let assetCollection = collection as? PHAssetCollection {
        collections.append(assetCollection)
      }
    }
    return collections
  }

  func get(by ids: [String]) -> [PHAssetCollection] {
    var collections: [PHAssetCollection] = []
    let pHFetchResult = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: ids, options: nil)
    pHFetchResult.enumerateObjects { collection, _, _ in
      collections.append(collection)
    }
    return collections
  }

  func get(by id: String) -> PHAssetCollection? {
    let pHFetchResult = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [id], options: nil)
    var fetchedCollection: PHAssetCollection?
    pHFetchResult.enumerateObjects { collection, _, _ in
      fetchedCollection = collection
    }
    return fetchedCollection
  }

  func get(containing asset: PHAsset) -> [PHAssetCollection] {
    var collections: [PHAssetCollection] = []
    let pHFetchResult = PHAssetCollection.fetchAssetCollectionsContaining(
      asset,
      with: .album,
      options: nil
    )
    pHFetchResult.enumerateObjects { collection, _, _ in
      collections.append(collection)
    }
    return collections
  }

  func get(byTitle title: String) -> PHAssetCollection? {
    let pHFetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: nil)
    var fetchedCollection: PHAssetCollection?
    pHFetchResult.enumerateObjects { collection, _, stop in
      if collection.localizedTitle == title {
        fetchedCollection = collection
        stop.pointee = true
      }
    }
    return fetchedCollection
  }

  func delete(by collectionIds: [String], deleteAssets: Bool = false) async throws {
    let albums = get(by: collectionIds)
    try await delete(by: albums, deleteAssets: deleteAssets)
  }

  func delete(by collections: [PHAssetCollection], deleteAssets: Bool = false) async throws {
    try await PHPhotoLibrary.shared().performChanges {
      if deleteAssets {
        let allAssets = AssetRepository.shared.get(by: collections)
        PHAssetChangeRequest.deleteAssets(allAssets as NSFastEnumeration)
      }
      PHAssetCollectionChangeRequest.deleteAssetCollections(collections as NSFastEnumeration)
    }
  }

  func add(name: String) async throws -> String {
    var collectionPlaceholder: PHObjectPlaceholder?

    try await PHPhotoLibrary.shared().performChanges {
      let createCollectionRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
      collectionPlaceholder = createCollectionRequest.placeholderForCreatedAssetCollection
    }

    guard let placeholder = collectionPlaceholder else {
      throw FailedToCreateAlbumException("Failed to create album")
    }
    return placeholder.localIdentifier
  }

  func add(assets: [PHAsset], to collectionId: String) async throws {
    guard let collection = get(by: collectionId) else {
      throw FailedToGetAlbumException("Collection not found")
    }
    try await add(assets: assets, to: collection)
  }

  func add(assets: [PHAsset], to collection: PHAssetCollection) async throws {
    try await PHPhotoLibrary.shared().performChanges {
      if let changeRequest = PHAssetCollectionChangeRequest(for: collection) {
        changeRequest.addAssets(assets as NSFastEnumeration)
      }
    }
  }

  func remove(assets: [PHAsset], from collection: PHAssetCollection) async throws {
    try await PHPhotoLibrary.shared().performChanges {
      let fetchResult = PHAsset.fetchAssets(in: collection, options: nil)
      if let changeRequest = PHAssetCollectionChangeRequest(for: collection, assets: fetchResult) {
        changeRequest.removeAssets(assets as NSFastEnumeration)
      }
    }
  }
}
