#if canImport(CryptoSwift)
import CryptoSwift
#endif
import Foundation

extension Configuration {
    // MARK: Caching Configurations By Identifier (In-Memory)
    private static var cachedConfigurationsByIdentifier = [String: Configuration]()
    private static let cachedConfigurationsByIdentifierLock = NSLock()

    /// Since the cache is stored in a static var, this function is used to reset the cache during tests
    internal static func resetCache() {
        Self.cachedConfigurationsByIdentifierLock.lock()
        Self.cachedConfigurationsByIdentifier = [:]
        Self.cachedConfigurationsByIdentifierLock.unlock()
    }

    internal func setCached(forIdentifier identifier: String) {
        Self.cachedConfigurationsByIdentifierLock.lock()
        Self.cachedConfigurationsByIdentifier[identifier] = self
        Self.cachedConfigurationsByIdentifierLock.unlock()
    }

    internal static func getCached(forIdentifier identifier: String) -> Configuration? {
        cachedConfigurationsByIdentifierLock.lock()
        defer { cachedConfigurationsByIdentifierLock.unlock() }
        return cachedConfigurationsByIdentifier[identifier]
    }

    /// Returns a copy of the current `Configuration` with its `computedCacheDescription` property set to the value of
    /// `cacheDescription`, which is expensive to compute.
    ///
    /// - returns: A new `Configuration` value.
    public func withPrecomputedCacheDescription() -> Configuration {
        var result = self
        result.computedCacheDescription = result.cacheDescription
        return result
    }

    // MARK: Nested Config Is Self Cache
    private static var nestedConfigIsSelfByIdentifier = [String: Bool]()
    private static let nestedConfigIsSelfByIdentifierLock = NSLock()

    internal static func setIsNestedConfigurationSelf(forIdentifier identifier: String, value: Bool) {
        Self.nestedConfigIsSelfByIdentifierLock.lock()
        Self.nestedConfigIsSelfByIdentifier[identifier] = value
        Self.nestedConfigIsSelfByIdentifierLock.unlock()
    }

    internal static func getIsNestedConfigurationSelf(forIdentifier identifier: String) -> Bool {
        Self.nestedConfigIsSelfByIdentifierLock.lock()
        defer { Self.nestedConfigIsSelfByIdentifierLock.unlock() }
        return Self.nestedConfigIsSelfByIdentifier[identifier] ?? false
    }

    // MARK: SwiftLint Cache (On-Disk)
    internal var cacheDescription: String {
        if let computedCacheDescription {
            return computedCacheDescription
        }

        let cacheRulesDescriptions = rules
            .map { rule in [type(of: rule).identifier, rule.cacheDescription] }
            .sorted { $0[0] < $1[0] }
        let jsonObject: [Any] = [rootDirectory.filepath, cacheRulesDescriptions]
        if let jsonData = try? JSONSerialization.data(withJSONObject: jsonObject) {
            return jsonData.sha256().toHexString()
        }
        queuedFatalError("Could not serialize configuration for cache")
    }

    internal var cacheURL: URL {
        let baseURL: URL
        if let path = cachePath {
            baseURL = URL(filePath: path, directoryHint: .isDirectory)
        } else {
#if os(Linux)
            baseURL = URL(filePath: "/var/tmp/", directoryHint: .isDirectory)
#else
            baseURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
#endif
        }

        var folder = baseURL
                .appending(path: "SwiftLint", directoryHint: .isDirectory)
                .appending(path: Version.current.value, directoryHint: .isDirectory)
        if let buildID = ExecutableInfo.buildID {
            folder = folder.appending(path: buildID, directoryHint: .isDirectory)
        }

        do {
            try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
        } catch {
            Issue.genericWarning("Cannot create cache: " + error.localizedDescription).print()
        }

        return folder
    }
}
