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

/// Responsible for migrating Storage Service record encryption to use a
/// `recordIkm` stored in the Storage Service manifest.
public protocol StorageServiceRecordIkmMigrator {

    /// Schedules a migration to encrypting Storage Service records using a
    /// `recordIkm` from the Storage Service manifest, if necessary.
    func migrateToManifestRecordIkmIfNecessary() async
}

struct StorageServiceRecordIkmMigratorImpl: StorageServiceRecordIkmMigrator {
    private let logger = PrefixedLogger(prefix: "[SSRecIkmMig]")

    private let db: any DB
    private let storageServiceManager: StorageServiceManager
    private let tsAccountManager: TSAccountManager

    init(
        db: any DB,
        storageServiceManager: StorageServiceManager,
        tsAccountManager: TSAccountManager,
    ) {
        self.db = db
        self.storageServiceManager = storageServiceManager
        self.tsAccountManager = tsAccountManager
    }

    // MARK: -

    func migrateToManifestRecordIkmIfNecessary() async {
        /// If we're currently restoring, allow that to finish. We may be
        /// restoring (or creating) a manifest that contains a `recordIkm`,
        /// in which case there's nothing for us to do!
        try? await storageServiceManager.waitForPendingRestores()

        let (
            alreadyHasRecordIkm,
            isRegisteredPrimaryDevice,
        ): (
            Bool,
            Bool,
        ) = db.read { tx in
            return (
                storageServiceManager.currentManifestHasRecordIkm(tx: tx),
                tsAccountManager.registrationState(tx: tx).isRegisteredPrimaryDevice,
            )
        }

        guard isRegisteredPrimaryDevice else {
            owsFailDebug(
                "Unexpectedly attempting to migrate to recordIkm, but not a primary device!",
                logger: logger,
            )
            return
        }

        guard !alreadyHasRecordIkm else {
            return
        }

        logger.info("Rotating Storage Service manifest to ensure recordIkm is generated.")

        /// All we need to do is rotate the Storage Service manifest and
        /// referenced records. When we recreate a manifest and records (and the
        /// relevant capability is `true`, which we assert above) we will
        /// generate, store, and thereafter use a `recordIkm`.
        ///
        /// Note that rotating the manifest will send a sync message telling our
        /// other devices to fetch the latest manifest, and thereby learn about
        /// the `recordIkm` themselves.
        do {
            try await storageServiceManager.rotateManifest(
                mode: .alsoRotatingRecords,
                authedDevice: .implicit,
            )

            logger.info("Successfully rotated Storage Service manifest.")

            owsAssertDebug(
                db.read { storageServiceManager.currentManifestHasRecordIkm(tx: $0) },
                "Unexpectedly missing recordIkm after Storage Service manifest rotation!",
                logger: logger,
            )
        } catch {
            logger.error("Failed to rotate Storage Service manifest! \(error)")
        }
    }
}
