//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//

import XCTest

@testable import Amplify
@testable import AmplifyTestCommon
@testable import AWSDataStorePlugin
@testable import AWSPluginsCore

class MutationSyncMetadataMigrationTests: MutationSyncMetadataMigrationTestBase {

    func testApply_MissingDelegate() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        let migration = MutationSyncMetadataMigration(delegate: delegate)
        migration.delegate = nil

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [])
            return
        }
        XCTFail("Should catch error")
    }

    func testApply_PreconditionFailure() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.preconditionCheckError = DataStoreError.internalOperation("Failure", "", nil)
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [.precondition])
            return
        }
        XCTFail("Should catch error")
    }

    func testApply_TransactionFailure() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.transactionError = DataStoreError.internalOperation("Failure", "", nil)
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [.precondition, .transaction])
            return
        }
        XCTFail("Should catch error")
    }

    func testApply_NeedsMigrationFailure() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.mutationSyncMetadataStoreEmptyOrMigratedError = DataStoreError.internalOperation("Failure", "", nil)
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [.precondition, .transaction, .mutationSyncMetadataStoreEmptyOrMigrated])
            return
        }
        XCTFail("Should catch error")
    }

    func testApply_CannotMigrateFailure() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.containsDuplicateIdsAcrossModelsError = DataStoreError.internalOperation("Failure", "", nil)
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [
                .precondition,
                .transaction,
                .mutationSyncMetadataStoreEmptyOrMigrated,
                .containsDuplicateIdsAcrossModels
            ])

            return
        }
        XCTFail("Should catch error")
    }

    func testApply_emptyMutationSyncMetadataStoreFailure() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.containsDuplicateIdsAcrossModelsResult = true
        delegate.emptyMutationSyncMetadataStoreError = DataStoreError.internalOperation("Failure", "", nil)
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [
                .precondition,
                .transaction,
                .mutationSyncMetadataStoreEmptyOrMigrated,
                .containsDuplicateIdsAcrossModels,
                .emptyMutationSyncMetadataStore
            ])
            return
        }
        XCTFail("Should catch error")
    }

    func testApply_emptyModelSyncMetadataStoreFailure() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.containsDuplicateIdsAcrossModelsResult = true
        delegate.emptyModelSyncMetadataStoreError = DataStoreError.internalOperation("Failure", "", nil)
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [
                .precondition,
                .transaction,
                .mutationSyncMetadataStoreEmptyOrMigrated,
                .containsDuplicateIdsAcrossModels,
                .emptyMutationSyncMetadataStore,
                .emptyModelSyncMetadataStore
            ])
            return
        }
        XCTFail("Should catch error")
    }

    func testApply_MigrateFailure() {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.mutationSyncMetadataStoreEmptyOrMigratedResult = false
        delegate.containsDuplicateIdsAcrossModelsResult = false
        delegate.removeMutationSyncMetadataCopyStoreError = DataStoreError.internalOperation("Failure", "", nil)
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        do {
            try migration.apply()
        } catch {
            XCTAssertEqual(delegate.stepsCalled, [
                .precondition,
                .transaction,
                .mutationSyncMetadataStoreEmptyOrMigrated,
                .containsDuplicateIdsAcrossModels,
                .removeMutationSyncMetadataCopyStore
            ])
            return
        }
        XCTFail("Should catch error")
    }

    func testApplyMigrate() throws {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.mutationSyncMetadataStoreEmptyOrMigratedResult = false
        delegate.containsDuplicateIdsAcrossModelsResult = false
        let migration = MutationSyncMetadataMigration(delegate: delegate)
        try migration.apply()
        XCTAssertEqual(delegate.stepsCalled, [
            .precondition,
            .transaction,
            .mutationSyncMetadataStoreEmptyOrMigrated,
            .containsDuplicateIdsAcrossModels,
            .removeMutationSyncMetadataCopyStore,
            .createMutationSyncMetadataCopyStore,
            .backfillMutationSyncMetadata,
            .removeMutationSyncMetadataStore,
            .renameMutationSyncMetadataCopy
        ])
    }

    func testApply() throws {
        try setUpAllModels()
        let restaurant = Restaurant(restaurantName: "name")
        save(restaurant)
        let metadata = MutationSyncMetadata(id: restaurant.id, deleted: false, lastChangedAt: 1, version: 1)
        saveMutationSyncMetadata(metadata)
        guard let mutationSyncMetadatas = queryMutationSyncMetadata() else {
            XCTFail("Could not get metadata")
            return
        }
        XCTAssertEqual(mutationSyncMetadatas.count, 1)
        XCTAssertEqual(mutationSyncMetadatas[0].id, restaurant.id)

        let delegate = SQLiteMutationSyncMetadataMigrationDelegate(
            storageAdapter: storageAdapter,
            modelSchemas: modelSchemas
        )
        let migration = MutationSyncMetadataMigration(delegate: delegate)

        try migration.apply()

        guard let mutationSyncMetadatasBackfilled = queryMutationSyncMetadata() else {
            XCTFail("Could not get metadata")
            return
        }
        XCTAssertEqual(mutationSyncMetadatasBackfilled.count, 1)

        XCTAssertEqual(mutationSyncMetadatasBackfilled[0].id, "\(Restaurant.modelName)|\(restaurant.id)")

        guard let restaurantMetadata = try storageAdapter.queryMutationSyncMetadata(
            for: restaurant.id,
            modelName: Restaurant.modelName
        ) else {
                   XCTFail("Could not get metadata")
                   return
               }
        XCTAssertEqual(restaurantMetadata.modelId, restaurant.id)
        XCTAssertEqual(restaurantMetadata.modelName, Restaurant.modelName)
    }

    func testApplyClear() throws {
        let delegate = MockMutationSyncMetadataMigrationDelegate()
        delegate.mutationSyncMetadataStoreEmptyOrMigratedResult = false
        delegate.containsDuplicateIdsAcrossModelsResult = true
        let migration = MutationSyncMetadataMigration(delegate: delegate)
        try migration.apply()
        XCTAssertEqual(delegate.stepsCalled, [
            .precondition,
            .transaction,
            .mutationSyncMetadataStoreEmptyOrMigrated,
            .containsDuplicateIdsAcrossModels,
            .emptyMutationSyncMetadataStore,
            .emptyModelSyncMetadataStore
        ])
    }
}
