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

import Foundation
import LibSignalClient
import SignalRingRTC
import SignalServiceKit
import SignalUI

class CallKitIdStore {
    private static let phoneNumberStore = KeyValueStore(collection: "TSStorageManagerCallKitIdToPhoneNumberCollection")
    private static let serviceIdStore = KeyValueStore(collection: "TSStorageManagerCallKitIdToUUIDCollection")
    private static let groupIdStore = KeyValueStore(collection: "TSStorageManagerCallKitIdToGroupId")
    private static let callLinkStore = KeyValueStore(collection: "CallKitIdToCallLink")

    static func setGroupId(_ groupId: GroupIdentifier, forCallKitId callKitId: String) {
        SSKEnvironment.shared.databaseStorageRef.write { tx in
            // Make sure it doesn't exist, but only in DEBUG builds.
            assert(!phoneNumberStore.hasValue(callKitId, transaction: tx))
            assert(!serviceIdStore.hasValue(callKitId, transaction: tx))
            assert(!groupIdStore.hasValue(callKitId, transaction: tx))
            assert(!callLinkStore.hasValue(callKitId, transaction: tx))

            groupIdStore.setData(groupId.serialize(), key: callKitId, transaction: tx)
        }
    }

    static func setContactThread(_ thread: TSContactThread, forCallKitId callKitId: String) {
        SSKEnvironment.shared.databaseStorageRef.write { tx in
            // Make sure it doesn't exist, but only in DEBUG builds.
            assert(!phoneNumberStore.hasValue(callKitId, transaction: tx))
            assert(!serviceIdStore.hasValue(callKitId, transaction: tx))
            assert(!groupIdStore.hasValue(callKitId, transaction: tx))
            assert(!callLinkStore.hasValue(callKitId, transaction: tx))

            let address = thread.contactAddress
            if let serviceIdString = address.serviceIdUppercaseString {
                serviceIdStore.setString(serviceIdString, key: callKitId, transaction: tx)
            } else if let phoneNumber = address.phoneNumber {
                owsFailDebug("making a call to an address with no UUID")
                phoneNumberStore.setString(phoneNumber, key: callKitId, transaction: tx)
            } else {
                owsFailDebug("making a call to an address with no phone number or uuid")
            }
        }
    }

    static func setCallLink(_ callLink: CallLink, forCallKitId callKitId: String) {
        SSKEnvironment.shared.databaseStorageRef.write { tx in
            // Make sure it doesn't exist, but only in DEBUG builds.
            assert(!phoneNumberStore.hasValue(callKitId, transaction: tx))
            assert(!serviceIdStore.hasValue(callKitId, transaction: tx))
            assert(!groupIdStore.hasValue(callKitId, transaction: tx))
            // Call Links may be stored multiple times...

            callLinkStore.setData(callLink.rootKey.bytes, key: callKitId, transaction: tx)
        }
    }

    static func callTarget(forCallKitId callKitId: String) -> CallTarget? {
        return SSKEnvironment.shared.databaseStorageRef.read { tx -> CallTarget? in
            // Most likely: modern 1:1 calls
            if let serviceIdString = serviceIdStore.getString(callKitId, transaction: tx) {
                let address = SignalServiceAddress(serviceIdString: serviceIdString)
                return TSContactThread.getWithContactAddress(address, transaction: tx).map { .individual($0) }
            }

            // Next try group calls
            if
                let groupIdData = groupIdStore.getData(callKitId, transaction: tx),
                let groupId = try? GroupIdentifier(contents: groupIdData)
            {
                return .groupThread(groupId)
            }

            // Check the phone number store, for very old 1:1 calls.
            if let phoneNumber = phoneNumberStore.getString(callKitId, transaction: tx) {
                let address = SignalServiceAddress.legacyAddress(serviceIdString: nil, phoneNumber: phoneNumber)
                return TSContactThread.getWithContactAddress(address, transaction: tx).map { .individual($0) }
            }

            if let rootKeyBytes = callLinkStore.getData(callKitId, transaction: tx) {
                return (try? CallLinkRootKey(rootKeyBytes)).map { .callLink(CallLink(rootKey: $0)) }
            }

            return nil
        }
    }
}
