//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
import GRDB

@objc
public class SSKPreferences: NSObject {

    public static let store = SDSKeyValueStore(collection: "SSKPreferences")

    private var store: SDSKeyValueStore {
        return SSKPreferences.store
    }

    // MARK: -

    private static var areLinkPreviewsEnabledKey: String { "areLinkPreviewsEnabled" }
    private static var areLegacyLinkPreviewsEnabledKey: String { "areLegacyLinkPreviewsEnabled" }

    @objc
    public static func areLinkPreviewsEnabled(transaction: SDSAnyReadTransaction) -> Bool {
        return store.getBool(areLinkPreviewsEnabledKey, defaultValue: true, transaction: transaction)
    }

    @objc
    public static func setAreLinkPreviewsEnabled(_ newValue: Bool,
                                                 sendSyncMessage shouldSync: Bool = false,
                                                 transaction: SDSAnyWriteTransaction) {
        store.setBool(newValue, key: areLinkPreviewsEnabledKey, transaction: transaction)

        if shouldSync {
            Self.syncManager.sendConfigurationSyncMessage()
            Self.storageServiceManager.recordPendingLocalAccountUpdates()
        }
    }

    // The following two methods are just to make sure we can store and forward in storage service updates
    public static func areLegacyLinkPreviewsEnabled(transaction: SDSAnyReadTransaction) -> Bool {
        return store.getBool(areLegacyLinkPreviewsEnabledKey, defaultValue: true, transaction: transaction)
    }

    public static func setAreLegacyLinkPreviewsEnabled(_ newValue: Bool, transaction: SDSAnyWriteTransaction) {
        store.setBool(newValue, key: areLegacyLinkPreviewsEnabledKey, transaction: transaction)
    }

    // MARK: -

    private static var areIntentDonationsEnabledKey: String { "areSharingSuggestionsEnabled" }

    @objc
    public static func areIntentDonationsEnabled(transaction: SDSAnyReadTransaction) -> Bool {
        return store.getBool(areIntentDonationsEnabledKey, defaultValue: true, transaction: transaction)
    }

    @objc
    public static func setAreIntentDonationsEnabled(_ newValue: Bool, transaction: SDSAnyWriteTransaction) {
        store.setBool(newValue, key: areIntentDonationsEnabledKey, transaction: transaction)
    }

    // MARK: -

    @objc
    public static func hasSavedThread(transaction: SDSAnyReadTransaction) -> Bool {
        return shared.hasSavedThread(transaction: transaction)
    }

    @objc
    public static func setHasSavedThread(_ newValue: Bool, transaction: SDSAnyWriteTransaction) {
        shared.setHasSavedThread(newValue, transaction: transaction)
    }

    private let hasSavedThreadKey = "hasSavedThread"
    // Only access this queue within db transactions.
    private var hasSavedThreadCache: Bool?

    @objc
    public func hasSavedThread(transaction: SDSAnyReadTransaction) -> Bool {
        if let value = hasSavedThreadCache {
            return value
        }
        let value = store.getBool(hasSavedThreadKey, defaultValue: false, transaction: transaction)
        hasSavedThreadCache = value
        return value
    }

    @objc
    public func setHasSavedThread(_ newValue: Bool, transaction: SDSAnyWriteTransaction) {
        store.setBool(newValue, key: hasSavedThreadKey, transaction: transaction)
        hasSavedThreadCache = newValue
    }

    // MARK: -

    private static var didDropYdbKey: String { "didDropYdb" }
    private static var didEverUseYdbKey: String { "didEverUseYdb" }

    @objc
    public static func clearLegacyDatabaseFlags(from userDefaults: UserDefaults) {
        userDefaults.removeObject(forKey: didDropYdbKey)
        userDefaults.removeObject(forKey: didEverUseYdbKey)
    }

    // MARK: - messageRequestInteractionIdEpoch

    private static var messageRequestInteractionIdEpochKey: String { "messageRequestInteractionIdEpoch" }
    private static var messageRequestInteractionIdEpochCached: Int?
    public static func messageRequestInteractionIdEpoch(transaction: GRDBReadTransaction) -> Int? {
        if let value = messageRequestInteractionIdEpochCached {
            return value
        }
        let value = store.getInt(messageRequestInteractionIdEpochKey, transaction: transaction.asAnyRead)
        messageRequestInteractionIdEpochCached = value
        return value
    }

    public static func setMessageRequestInteractionIdEpoch(_ value: Int?, transaction: GRDBWriteTransaction) {
        guard let value = value else {
            store.removeValue(forKey: messageRequestInteractionIdEpochKey, transaction: transaction.asAnyWrite)
            messageRequestInteractionIdEpochCached = nil
            return
        }

        store.setInt(value, key: messageRequestInteractionIdEpochKey, transaction: transaction.asAnyWrite)
        messageRequestInteractionIdEpochCached = value
    }

    // MARK: - Badge Count

    private static let includeMutedThreadsInBadgeCount = "includeMutedThreadsInBadgeCount"

    public static func includeMutedThreadsInBadgeCount(transaction: SDSAnyReadTransaction) -> Bool {
        return store.getBool(includeMutedThreadsInBadgeCount, defaultValue: false, transaction: transaction)
    }

    public static func setIncludeMutedThreadsInBadgeCount(_ value: Bool, transaction: SDSAnyWriteTransaction) {
        store.setBool(value, key: includeMutedThreadsInBadgeCount, transaction: transaction)
    }

    // MARK: - Profile avatar preference

    @objc
    public static let preferContactAvatarsPreferenceDidChange = Notification.Name("PreferContactAvatarsPreferenceDidChange")
    private static var preferContactAvatarsKey: String { "preferContactAvatarsKey" }
    private static var preferContactAvatarsCached: Bool?

    @objc
    public static func preferContactAvatars(transaction: SDSAnyReadTransaction) -> Bool {
        if let value = preferContactAvatarsCached { return value }
        let value = store.getBool(preferContactAvatarsKey, defaultValue: false, transaction: transaction)
        preferContactAvatarsCached = value
        return value
    }

    @objc
    public static func setPreferContactAvatars(
        _ value: Bool,
        updateStorageService: Bool = true,
        transaction: SDSAnyWriteTransaction) {

        let oldValue = store.getBool(preferContactAvatarsKey, transaction: transaction)
        store.setBool(value, key: preferContactAvatarsKey, transaction: transaction)
        preferContactAvatarsCached = value

        if oldValue != value {
            if updateStorageService {
                Self.storageServiceManager.recordPendingLocalAccountUpdates()
            }
            NotificationCenter.default.postNotificationNameAsync(Self.preferContactAvatarsPreferenceDidChange, object: nil)
        }
    }

    // MARK: -

    public class var grdbSchemaVersionDefault: UInt {
        return GRDBSchemaMigrator.grdbSchemaVersionDefault
    }
    public class var grdbSchemaVersionLatest: UInt {
        return GRDBSchemaMigrator.grdbSchemaVersionLatest
    }

    private static var grdbSchemaVersionKey: String { "grdbSchemaVersion" }

    private static func grdbSchemaVersion() -> UInt {
        let appUserDefaults = CurrentAppContext().appUserDefaults()
        guard let preference = appUserDefaults.object(forKey: grdbSchemaVersionKey) as? NSNumber else {
            return grdbSchemaVersionDefault
        }
        return preference.uintValue
    }

    private static func setGrdbSchemaVersion(_ value: UInt) {
        let lastKnownGrdbSchemaVersion = grdbSchemaVersion()
        guard value != lastKnownGrdbSchemaVersion else {
            return
        }
        guard value > lastKnownGrdbSchemaVersion else {
            owsFailDebug("Reverting to earlier schema version: \(value)")
            return
        }
        Logger.info("Updating schema version: \(lastKnownGrdbSchemaVersion) -> \(value)")
        let appUserDefaults = CurrentAppContext().appUserDefaults()
        appUserDefaults.set(value, forKey: grdbSchemaVersionKey)
        appUserDefaults.synchronize()
    }

    @objc
    public static func markGRDBSchemaAsLatest() {
        setGrdbSchemaVersion(grdbSchemaVersionLatest)
    }

    @objc
    public static func hasUnknownGRDBSchema() -> Bool {
        guard grdbSchemaVersion() <= grdbSchemaVersionLatest else {
            owsFailDebug("grdbSchemaVersion: \(grdbSchemaVersion()), grdbSchemaVersionLatest: \(grdbSchemaVersionLatest)")
            return true
        }
        return false
    }

    // MARK: - Keep Muted Chats Archived

    private static var shouldKeepMutedChatsArchivedKey: String { "shouldKeepMutedChatsArchived" }

    @objc
    public static func shouldKeepMutedChatsArchived(transaction: SDSAnyReadTransaction) -> Bool {
        return store.getBool(shouldKeepMutedChatsArchivedKey, defaultValue: false, transaction: transaction)
    }

    @objc
    public static func setShouldKeepMutedChatsArchived(_ newValue: Bool, transaction: SDSAnyWriteTransaction) {
        store.setBool(newValue, key: shouldKeepMutedChatsArchivedKey, transaction: transaction)
    }
}
