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

import Foundation
import SQLite
import XCTest

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

class StorageEngineTestsManyToMany: StorageEngineTestsBase {

    override func setUp() {
        super.setUp()
        Amplify.Logging.logLevel = .warn

        let validAPIPluginKey = "MockAPICategoryPlugin"
        let validAuthPluginKey = "MockAuthCategoryPlugin"
        do {
            connection = try Connection(.inMemory)
            storageAdapter = try SQLiteStorageEngineAdapter(connection: connection)
            try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas)

            syncEngine = MockRemoteSyncEngine()
            storageEngine = StorageEngine(
                storageAdapter: storageAdapter,
                dataStoreConfiguration: .testDefault(),
                syncEngine: syncEngine,
                validAPIPluginKey: validAPIPluginKey,
                validAuthPluginKey: validAuthPluginKey
            )
            ModelRegistry.register(modelType: M2MPost.self)
            ModelRegistry.register(modelType: M2MPostEditor.self)
            ModelRegistry.register(modelType: M2MUser.self)
            do {
                try storageEngine.setUp(modelSchemas: [M2MPost.schema])
                try storageEngine.setUp(modelSchemas: [M2MPostEditor.schema])
                try storageEngine.setUp(modelSchemas: [M2MUser.schema])

            } catch {
                XCTFail("Failed to setup storage engine")
            }
        } catch {
            XCTFail(String(describing: error))
            return
        }
    }

    func testDeletePostAndPostEditor() {
        var post1 = M2MPost(title: "Post1")
        var user1 = M2MUser(username: "User1")
        let postEditors1 = M2MPostEditor(post: post1, editor: user1)
        post1.editors = [postEditors1]
        user1.posts = [postEditors1]

        var post2 = M2MPost(title: "Post2")
        var user2 = M2MUser(username: "User2")
        let postEditors2 = M2MPostEditor(post: post2, editor: user2)
        post2.editors = [postEditors2]
        user2.posts = [postEditors2]

        guard case .success = saveModelSynchronous(model: user1),
            case .success = saveModelSynchronous(model: post1),
            case .success = saveModelSynchronous(model: postEditors1),
            case .success = saveModelSynchronous(model: user2),
            case .success = saveModelSynchronous(model: post2),
            case .success = saveModelSynchronous(model: postEditors2) else {
                XCTFail("Failed to save hierachy")
                return
        }

        guard case .success =
            querySingleModelSynchronous(
                modelType: M2MPost.self,
                predicate: M2MPost.keys.id == post1.id
            ) else {
                                            XCTFail("Failed to query M2MPost")
                                            return
        }
        guard case .success =
            querySingleModelSynchronous(
                modelType: M2MPostEditor.self,
                predicate: M2MPostEditor.keys.id == postEditors1.id
            ) else {
                                            XCTFail("Failed to query M2MPostEditor")
                                            return
        }
        guard case .success =
            querySingleModelSynchronous(
                modelType: M2MUser.self,
                predicate: M2MUser.keys.id == user1.id
            ) else {
                                            XCTFail("Failed to query M2MUser")
                                            return
        }

        let mutationEvents = expectation(description: "Mutation Events submitted to sync engine")
        mutationEvents.expectedFulfillmentCount = 2

        syncEngine.setCallbackOnSubmit{ submittedMutationEvent, completion in
            mutationEvents.fulfill()
            completion(.success(submittedMutationEvent))
        }
        guard case .success = deleteModelSynchronousOrFailOtherwise(
            modelType: M2MPost.self,
            withId: post1.id
        ) else {
            XCTFail("Failed to delete post1")
            return
        }
        wait(for: [mutationEvents], timeout: defaultTimeout)
    }

    func testDeleteUserAndPostEditor() {
        var post1 = M2MPost(title: "Post1")
        var user1 = M2MUser(username: "User1")
        let postEditors1 = M2MPostEditor(post: post1, editor: user1)
        post1.editors = [postEditors1]
        user1.posts = [postEditors1]

        var post2 = M2MPost(title: "Post2")
        var user2 = M2MUser(username: "User2")
        let postEditors2 = M2MPostEditor(post: post2, editor: user2)
        post2.editors = [postEditors2]
        user2.posts = [postEditors2]

        guard case .success = saveModelSynchronous(model: user1),
            case .success = saveModelSynchronous(model: post1),
            case .success = saveModelSynchronous(model: postEditors1),
            case .success = saveModelSynchronous(model: user2),
            case .success = saveModelSynchronous(model: post2),
            case .success = saveModelSynchronous(model: postEditors2) else {
                XCTFail("Failed to save hierachy")
                return
        }

        guard case .success =
            querySingleModelSynchronous(
                modelType: M2MPost.self,
                predicate: M2MPost.keys.id == post1.id
            ) else {
                                            XCTFail("Failed to query M2MPost")
                                            return
        }
        guard case .success =
            querySingleModelSynchronous(
                modelType: M2MPostEditor.self,
                predicate: M2MPostEditor.keys.id == postEditors1.id
            ) else {
                                            XCTFail("Failed to query M2MPostEditor")
                                            return
        }
        guard case .success =
            querySingleModelSynchronous(
                modelType: M2MUser.self,
                predicate: M2MUser.keys.id == user1.id
            ) else {
                                            XCTFail("Failed to query M2MUser")
                                            return
        }

        let mutationEvents = expectation(description: "Mutation Events submitted to sync engine")
        mutationEvents.expectedFulfillmentCount = 2

        syncEngine.setCallbackOnSubmit{ submittedMutationEvent, completion in
            mutationEvents.fulfill()
            completion(.success(submittedMutationEvent))
        }
        guard case .success = deleteModelSynchronousOrFailOtherwise(
            modelType: M2MUser.self,
            withId: user1.id
        ) else {
            XCTFail("Failed to delete post1")
            return
        }
        wait(for: [mutationEvents], timeout: defaultTimeout)
    }
}
