import Foundation
import CocoaLumberjackSwift

enum CacheFileWriterError: Error {
    case missingTemporaryFileURL
    case missingHeaderItemKey
    case missingHTTPResponse
    case unableToDetermineSiteURLFromMigration
    case unexpectedFetcherTypeForBundledMigration
    case unableToDetermineBundledOfflineURLS
    case failureToSaveBundledFiles
    case unableToPullCachedDataFromNotModified
    case missingURLInRequest
    case unableToGenerateHTTPURLResponse
    case unableToDetermineFileNames
}

enum CacheFileWriterAddResult {
    case success(response: HTTPURLResponse, data: Data)
    case failure(Error)
}

enum CacheFileWriterRemoveResult {
    case success
    case failure(Error)
}

final class CacheFileWriter: CacheTaskTracking {

    private let fetcher: CacheFetching
    
    lazy private var baseCSSFileURL: URL = {
        URL(fileURLWithPath: WikipediaAppUtils.assetsPath())
            .appendingPathComponent("pcs-html-converter", isDirectory: true)
            .appendingPathComponent("base.css", isDirectory: false)
    }()

    lazy private var pcsCSSFileURL: URL = {
        URL(fileURLWithPath: WikipediaAppUtils.assetsPath())
            .appendingPathComponent("pcs-html-converter", isDirectory: true)
            .appendingPathComponent("pcs.css", isDirectory: false)
    }()

    lazy private var pcsJSFileURL: URL = {
        URL(fileURLWithPath: WikipediaAppUtils.assetsPath())
            .appendingPathComponent("pcs-html-converter", isDirectory: true)
            .appendingPathComponent("pcs.js", isDirectory: false)
    }()
    
    var groupedTasks: [String : [IdentifiedTask]] = [:]
    
    init(fetcher: CacheFetching) {
        self.fetcher = fetcher
        
        do {
            try FileManager.default.createDirectory(at: CacheController.cacheURL, withIntermediateDirectories: true, attributes: nil)
        } catch {
            DDLogError("Error creating permanent cache: \(error)")
        }
    }
    
    func add(groupKey: String, urlRequest: URLRequest, completion: @escaping (CacheFileWriterAddResult) -> Void) {
        
        let untrackKey = UUID().uuidString
        let task = fetcher.dataForURLRequest(urlRequest) { [weak self] (response) in
            guard let self = self else {
                return
            }
            
            defer {
                self.untrackTask(untrackKey: untrackKey, from: groupKey)
            }
            
            switch response {
            case .success(let result):
                
                guard let httpUrlResponse = result.response as? HTTPURLResponse else {
                    completion(.failure(CacheFileWriterError.missingHTTPResponse))
                    return
                }
                
                self.fetcher.cacheResponse(httpUrlResponse: httpUrlResponse, content: .data(result.data), urlRequest: urlRequest, success: {
                    completion(.success(response: httpUrlResponse, data: result.data))
                }) { (error) in
                    completion(.failure(error))
                }
                
            case .failure(let error):
                DDLogError("Error downloading data for offline: \(error)\n\(String(describing: response))")
                completion(.failure(error))
                return
            }
        }
        
        if let task = task {
            trackTask(untrackKey: untrackKey, task: task, to: groupKey)
        }
    }
    
    func remove(itemKey: String, variant: String?, completion: @escaping (CacheFileWriterRemoveResult) -> Void) {
        
        guard let fileName = self.fetcher.uniqueFileNameForItemKey(itemKey, variant: variant),
            let headerFileName = self.fetcher.uniqueHeaderFileNameForItemKey(itemKey, variant: variant) else {
                completion(.failure(CacheFileWriterError.unableToDetermineFileNames))
                return
        }
        
        var responseHeaderRemoveError: Error? = nil
        var responseRemoveError: Error? = nil

        // remove response from file system
        let responseCachedFileURL = CacheFileWriterHelper.fileURL(for: fileName)
        do {
            try FileManager.default.removeItem(at: responseCachedFileURL)
        } catch let error as NSError {
            if !(error.code == NSURLErrorFileDoesNotExist || error.code == NSFileNoSuchFileError) {
               responseRemoveError = error
            }
        }
        
        // remove response header from file system
        let responseHeaderCachedFileURL = CacheFileWriterHelper.fileURL(for: headerFileName)
        do {
            try FileManager.default.removeItem(at: responseHeaderCachedFileURL)
        } catch let error as NSError {
            if !(error.code == NSURLErrorFileDoesNotExist || error.code == NSFileNoSuchFileError) {
               responseHeaderRemoveError = error
            }
        }
        
        if let responseHeaderRemoveError = responseHeaderRemoveError {
            completion(.failure(responseHeaderRemoveError))
            return
        }
        
        if let responseRemoveError = responseRemoveError {
            completion(.failure(responseRemoveError))
            return
        }
        
        completion(.success)
    }
    
    func uniqueFileNameForItemKey(_ itemKey: CacheController.ItemKey, variant: String?) -> String? {
        return fetcher.uniqueFileNameForItemKey(itemKey, variant: variant)
    }
    
    func uniqueFileNameForURLRequest(_ urlRequest: URLRequest) -> String? {
        return fetcher.uniqueFileNameForURLRequest(urlRequest)
    }
    
}

// MARK: Migration

extension CacheFileWriter {
    
    // assumes urlRequest is already populated with the right type header
    func addMobileHtmlContentForMigration(content: String, urlRequest: URLRequest, success: @escaping () -> Void, failure: @escaping (Error) -> Void) {
        
        guard let url =  urlRequest.url else {
                failure(CacheFileWriterError.missingURLInRequest)
                return
        }
        
        // artificially create HTTPURLResponse
        guard let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: ["Content-Type": "text/html"]) else {
            failure(CacheFileWriterError.unableToGenerateHTTPURLResponse)
            return
        }
        
        fetcher.cacheResponse(httpUrlResponse: response, content: .string(content), urlRequest: urlRequest, success: {
            success()
        }) { (error) in
            failure(error)
        }
    }
    
    func addBundledResourcesForMigration(urlRequests:[URLRequest], success: @escaping ([URLRequest]) -> Void, failure: @escaping (Error) -> Void) {
        
        guard let articleFetcher = fetcher as? ArticleFetcher else {
            failure(CacheFileWriterError.unexpectedFetcherTypeForBundledMigration)
            return
        }
        
        guard let bundledOfflineResources = articleFetcher.bundledOfflineResourceURLs() else {
            failure(CacheFileWriterError.unableToDetermineBundledOfflineURLS)
            return
        }
        
        var failedURLRequests: [URLRequest] = []
        var succeededURLRequests: [URLRequest] = []
        
        for urlRequest in urlRequests {
            
            guard let urlString = urlRequest.url?.absoluteString else {
                continue
            }
            
            switch urlString {
            case bundledOfflineResources.baseCSS.absoluteString:
                
                fetcher.writeBundledFiles(mimeType: "text/css", bundledFileURL: baseCSSFileURL, urlRequest: urlRequest) { (result) in
                    switch result {
                    case .success:
                        succeededURLRequests.append(urlRequest)
                    case .failure:
                        failedURLRequests.append(urlRequest)
                    }
                }
                
            case bundledOfflineResources.pcsCSS.absoluteString:
                
                fetcher.writeBundledFiles(mimeType: "text/css", bundledFileURL: pcsCSSFileURL, urlRequest: urlRequest) { (result) in
                    switch result {
                    case .success:
                        succeededURLRequests.append(urlRequest)
                    case .failure:
                        failedURLRequests.append(urlRequest)
                    }
                }
                
            case bundledOfflineResources.pcsJS.absoluteString:
                
                fetcher.writeBundledFiles(mimeType: "application/javascript", bundledFileURL: pcsJSFileURL, urlRequest: urlRequest) { (result) in
                    switch result {
                    case .success:
                        succeededURLRequests.append(urlRequest)
                    case .failure:
                        failedURLRequests.append(urlRequest)
                    }
                }
                
            default:
                failedURLRequests.append(urlRequest)
            }
        }
        
        if succeededURLRequests.count == 0 {
            failure(CacheFileWriterError.failureToSaveBundledFiles)
            return
        }

        success(succeededURLRequests)
    }
}
