//
//  FeedlyCreateFeedsForCollectionFoldersOperationTests.swift
//  AccountTests
//
//  Created by Kiel Gillard on 22/10/19.
//  Copyright © 2019 Ranchero Software, LLC. All rights reserved.
//

import XCTest
@testable import Account

class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
	
	private var account: Account!
	private let support = FeedlyTestSupport()
	
	override func setUp() {
		super.setUp()
		account = support.makeTestAccount()
	}
	
	override func tearDown() {
		if let account = account {
			support.destroy(account)
		}
		super.tearDown()
	}
	
	class FeedsAndFoldersProvider: FeedlyFeedsAndFoldersProviding {
		var feedsAndFolders = [([FeedlyFeed], Folder)]()
	}
	
	func testAddFeeds() {
		let feedsForFolderOne = [
			FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil),
			FeedlyFeed(id: "feed/2", title: "Feed Two", updated: nil, website: nil)
		]
		
		let feedsForFolderTwo = [
			FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil),
			FeedlyFeed(id: "feed/3", title: "Feed Three", updated: nil, website: nil),
		]
		
		let folderOne: (name: String, id: String) = ("FolderOne", "folder/1")
		let folderTwo: (name: String, id: String) = ("FolderTwo", "folder/2")
		let namesAndFeeds = [(folderOne, feedsForFolderOne), (folderTwo, feedsForFolderTwo)]
		
		let provider = FeedsAndFoldersProvider()
		provider.feedsAndFolders = namesAndFeeds.map { (folder, feeds) in
			let accountFolder = account.ensureFolder(with: folder.name)!
			accountFolder.externalID = folder.id
			return (feeds, accountFolder)
		}
		
		let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: provider, log: support.log)
		let completionExpectation = expectation(description: "Did Finish")
		createFeeds.completionBlock = { _ in
			completionExpectation.fulfill()
		}
		
		XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
		
		MainThreadOperationQueue.shared.add(createFeeds)
		
		waitForExpectations(timeout: 2)
		
		let feedIds = Set([feedsForFolderOne, feedsForFolderTwo]
			.flatMap { $0 }
			.map { $0.id })
		
		let feedTitles = Set([feedsForFolderOne, feedsForFolderTwo]
			.flatMap { $0 }
			.map { $0.title })
		
		let accountFeeds = account.flattenedFeeds()
		let ingestedIds = Set(accountFeeds.map { $0.feedID })
		let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
		
		let missingIds = feedIds.subtracting(ingestedIds)
		let missingTitles = feedTitles.subtracting(ingestedTitles)
		
		XCTAssertTrue(missingIds.isEmpty, "Failed to ingest feeds with these ids.")
		XCTAssertTrue(missingTitles.isEmpty, "Failed to ingest feeds with these titles.")
		
		let expectedFolderAndFeedIds = namesAndFeeds
			.sorted { $0.0.id < $1.0.id }
			.map { folder, feeds -> [String: [String]] in
			return [folder.id: feeds.map { $0.id }.sorted(by: <)]
		}
		
		let ingestedFolderAndFeedIds = (account.folders ?? Set())
			.sorted { $0.externalID! < $1.externalID! }
			.compactMap { folder -> [String: [String]]? in
				return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
		}
		
		XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds in their corresponding folders.")
	}
	
	func testRemoveFeeds() {
		let folderOne: (name: String, id: String) = ("FolderOne", "folder/1")
		let folderTwo: (name: String, id: String) = ("FolderTwo", "folder/2")
		let feedToRemove = FeedlyFeed(id: "feed/1", title: "Feed One", updated: nil, website: nil)
		
		var feedsForFolderOne = [
			feedToRemove,
			FeedlyFeed(id: "feed/2", title: "Feed Two", updated: nil, website: nil)
		]
		
		var feedsForFolderTwo = [
			feedToRemove,
			FeedlyFeed(id: "feed/3", title: "Feed Three", updated: nil, website: nil),
		]
		
		// Add initial content.
		do {
			let namesAndFeeds = [(folderOne, feedsForFolderOne), (folderTwo, feedsForFolderTwo)]
			
			let provider = FeedsAndFoldersProvider()
			provider.feedsAndFolders = namesAndFeeds.map { (folder, feeds) in
				let accountFolder = account.ensureFolder(with: folder.name)!
				accountFolder.externalID = folder.id
				return (feeds, accountFolder)
			}
			
			let createFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: provider, log: support.log)
			let completionExpectation = expectation(description: "Did Finish")
			createFeeds.completionBlock = { _ in
				completionExpectation.fulfill()
			}
			
			XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
			
			MainThreadOperationQueue.shared.add(createFeeds)
			
			waitForExpectations(timeout: 2)
		}
		
		feedsForFolderOne.removeAll { $0.id == feedToRemove.id }
		feedsForFolderTwo.removeAll { $0.id == feedToRemove.id }
		let namesAndFeeds = [(folderOne, feedsForFolderOne), (folderTwo, feedsForFolderTwo)]
		
		let provider = FeedsAndFoldersProvider()
		provider.feedsAndFolders = namesAndFeeds.map { (folder, feeds) in
			let accountFolder = account.ensureFolder(with: folder.name)!
			accountFolder.externalID = folder.id
			return (feeds, accountFolder)
		}
		
		let removeFeeds = FeedlyCreateFeedsForCollectionFoldersOperation(account: account, feedsAndFoldersProvider: provider, log: support.log)
		let completionExpectation = expectation(description: "Did Finish")
		removeFeeds.completionBlock = { _ in
			completionExpectation.fulfill()
		}
		
		MainThreadOperationQueue.shared.add(removeFeeds)
		
		waitForExpectations(timeout: 2)
		
		let feedIds = Set([feedsForFolderOne, feedsForFolderTwo]
			.flatMap { $0 }
			.map { $0.id })
		
		let feedTitles = Set([feedsForFolderOne, feedsForFolderTwo]
			.flatMap { $0 }
			.map { $0.title })
		
		let accountFeeds = account.flattenedFeeds()
		let ingestedIds = Set(accountFeeds.map { $0.feedID })
		let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
		
		XCTAssertEqual(ingestedIds.count, feedIds.count)
		XCTAssertEqual(ingestedTitles.count, feedTitles.count)
		
		let missingIds = feedIds.subtracting(ingestedIds)
		let missingTitles = feedTitles.subtracting(ingestedTitles)
		
		XCTAssertTrue(missingIds.isEmpty, "Failed to ingest feeds with these ids.")
		XCTAssertTrue(missingTitles.isEmpty, "Failed to ingest feeds with these titles.")
		
		let expectedFolderAndFeedIds = namesAndFeeds
			.sorted { $0.0.id < $1.0.id }
			.map { folder, feeds -> [String: [String]] in
			return [folder.id: feeds.map { $0.id }.sorted(by: <)]
		}
		
		let ingestedFolderAndFeedIds = (account.folders ?? Set())
			.sorted { $0.externalID! < $1.externalID! }
			.compactMap { folder -> [String: [String]]? in
				return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
		}
		
		XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds to their corresponding folders.")
	}
}
