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

import Foundation
import LibSignalClient
import Testing

@testable import SignalServiceKit

struct RemoteConfigTests {
    @Test(arguments: [
        ("research.megaphone.1", "15b9729c-51ea-4ddb-b516-652befe78062", 1_000_000, 243_315),
        ("research.megaphone.2", "15b9729c-51ea-4ddb-b516-652befe78062", 1_000_000, 551_742),
        ("research.megaphone.1", "5f5b28bb-f485-4a0a-a85c-13fc047524b1", 1_000_000, 365_381),
        ("research.megaphone.1", "15b9729c-51ea-4ddb-b516-652befe78062", 100_000, 43_315),
    ])
    func bucketCalculation(testCase: (key: String, uuidString: String, bucketSize: UInt64, expectedBucket: UInt64)) {
        let actualBucket = RemoteConfig.bucket(key: testCase.key, aci: Aci.constantForTesting(testCase.uuidString), bucketSize: testCase.bucketSize)
        #expect(actualBucket == testCase.expectedBucket)
    }

    @Test(arguments: [
        ("1", true),
        ("true", true),
        ("TRUE", true),
        ("false", false),
        ("", false),
        ("11", false),
    ])
    func isEnabledFlag(testCase: (rawValue: String, isEnabled: Bool)) {
        let remoteConfig = RemoteConfig(clockSkew: 0, valueFlags: ["global.gifSearch": testCase.rawValue])
        #expect(remoteConfig.enableGifSearch == testCase.isEnabled)
    }

    @Test
    func testHotSwapping() {
        let remoteConfig = RemoteConfig(clockSkew: 0, valueFlags: [
            "test.hotSwappable.enabled": "false",
            "test.nonSwappable.enabled": "false",
            "test.hotSwappable.value": "abc",
            "test.nonSwappable.value": "abc",
        ])
        #expect(remoteConfig.testHotSwappable == false)
        #expect(remoteConfig.testNonSwappable == false)
        #expect(remoteConfig.testHotSwappableValue == "abc")
        #expect(remoteConfig.testNonSwappableValue == "abc")
        #expect(remoteConfig.lastKnownClockSkew == 0)

        let unchangedConfig = remoteConfig.merging(
            newValueFlags: nil,
            newClockSkew: 1,
        )
        #expect(unchangedConfig.testHotSwappable == false)
        #expect(unchangedConfig.testNonSwappable == false)
        #expect(unchangedConfig.testHotSwappableValue == "abc")
        #expect(unchangedConfig.testNonSwappableValue == "abc")
        #expect(unchangedConfig.lastKnownClockSkew == 1)

        let mergedEmptyConfig = remoteConfig.merging(
            newValueFlags: [:],
            newClockSkew: 2,
        )
        #expect(mergedEmptyConfig.testHotSwappable == nil)
        #expect(mergedEmptyConfig.testNonSwappable == false)
        #expect(mergedEmptyConfig.testHotSwappableValue == nil)
        #expect(mergedEmptyConfig.testNonSwappableValue == "abc")
        #expect(mergedEmptyConfig.lastKnownClockSkew == 2)

        let mergedConfig = remoteConfig.merging(
            newValueFlags: [
                "test.hotSwappable.enabled": "true",
                "test.nonSwappable.enabled": "true",
                "test.hotSwappable.value": "123",
                "test.nonSwappable.value": "123",
            ],
            newClockSkew: 3,
        )
        #expect(mergedConfig.testHotSwappable == true)
        #expect(mergedConfig.testNonSwappable == false)
        #expect(mergedConfig.testHotSwappableValue == "123")
        #expect(mergedConfig.testNonSwappableValue == "abc")
        #expect(mergedConfig.lastKnownClockSkew == 3)
    }

    @Test
    func testNetConfig() {
        let remoteConfig = RemoteConfig(clockSkew: 0, valueFlags: [
            "ios.libsignal.config1": "true",
            "ios.libsignal.config2": "false",
            "global.libsignal.config3": "value",
            "global.libsignal.config4": "4",
            "ios.libsignal.config4": "four",
        ])
        let netConfig = remoteConfig.netConfig()
        #expect(netConfig == [
            "config1": "true",
            "config4": "four",
        ])
    }
}

struct RemoteConfigStoreTests {
    let db = InMemoryDB()
    let keyValueStore = KeyValueStore(collection: "")
    let store: RemoteConfigStore

    init() {
        self.store = RemoteConfigStore(keyValueStore: self.keyValueStore)
    }

    @Test
    func migrationFallback() {
        self.db.write { tx in
            let isEnabledFlags: [String: Bool] = [
                "ios.abc": true,
                "ios.123": false,
            ]
            self.keyValueStore.setObject(isEnabledFlags as [NSString: NSNumber] as NSDictionary, key: "remoteConfigKey", transaction: tx)
        }
        let valueFlags = self.db.read { tx in
            return self.store.loadValueFlags(tx: tx)
        }
        #expect(valueFlags == ["ios.abc": "true", "ios.123": "false"])
    }

    @Test
    func migrationMerge() {
        self.db.write { tx in
            let valueFlags: [String: String] = [
                "ios.abc": "def",
                "ios.def": "ghi",
            ]
            let isEnabledFlags: [String: Bool] = [
                "ios.ghi": true,
                "ios.jkl": false,
            ]
            let timeGatedFlags: [String: Date] = [
                "ios.mno": Date(timeIntervalSince1970: 0),
                "ios.pqr": Date(timeIntervalSince1970: 1),
            ]
            self.keyValueStore.setObject(isEnabledFlags as [NSString: NSNumber] as NSDictionary, key: "remoteConfigKey", transaction: tx)
            self.keyValueStore.setObject(valueFlags as [NSString: NSString] as NSDictionary, key: "remoteConfigValueFlags", transaction: tx)
            self.keyValueStore.setObject(timeGatedFlags as [NSString: NSDate] as NSDictionary, key: "remoteConfigTimeGatedFlags", transaction: tx)
        }
        let valueFlags = self.db.read { tx in
            return self.store.loadValueFlags(tx: tx)
        }
        #expect(valueFlags == [
            "ios.abc": "def",
            "ios.def": "ghi",
            "ios.ghi": "true",
            "ios.jkl": "false",
            "ios.mno": "0.0",
            "ios.pqr": "1.0",
        ])
    }

    @Test
    func nilResult() {
        let valueFlags = self.db.read { tx in
            return self.store.loadValueFlags(tx: tx)
        }
        #expect(valueFlags == nil)
    }
}
