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

import SQLite
import XCTest

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

/// Tests behavior of local DataStore subscriptions (as opposed to remote API subscription behaviors)
class LocalSubscriptionTests: XCTestCase {

    override func setUp() async throws {
        try await super.setUp()

        await Amplify.reset()
        Amplify.Logging.logLevel = .warn

        let storageAdapter: SQLiteStorageEngineAdapter
        let storageEngine: StorageEngine
        var stateMachine: MockStateMachine<RemoteSyncEngine.State, RemoteSyncEngine.Action>!
        let validAPIPluginKey = "MockAPICategoryPlugin"
        let validAuthPluginKey = "MockAuthCategoryPlugin"
        do {
            let connection = try Connection(.inMemory)
            storageAdapter = try SQLiteStorageEngineAdapter(connection: connection)
            try storageAdapter.setUp(modelSchemas: StorageEngine.systemModelSchemas)

            let outgoingMutationQueue = NoOpMutationQueue()
            let mutationDatabaseAdapter = try AWSMutationDatabaseAdapter(storageAdapter: storageAdapter)
            let awsMutationEventPublisher = AWSMutationEventPublisher(eventSource: mutationDatabaseAdapter)
            stateMachine = MockStateMachine(
                initialState: .notStarted,
                resolver: RemoteSyncEngine.Resolver.resolve(currentState:action:)
            )

            let syncEngine = RemoteSyncEngine(
                storageAdapter: storageAdapter,
                dataStoreConfiguration: .testDefault(),
                authModeStrategy: AWSDefaultAuthModeStrategy(),
                outgoingMutationQueue: outgoingMutationQueue,
                mutationEventIngester: mutationDatabaseAdapter,
                mutationEventPublisher: awsMutationEventPublisher,
                initialSyncOrchestratorFactory: NoOpInitialSyncOrchestrator.factory,
                reconciliationQueueFactory: MockAWSIncomingEventReconciliationQueue.factory,
                stateMachine: stateMachine,
                networkReachabilityPublisher: nil,
                requestRetryablePolicy: MockRequestRetryablePolicy()
            )

            storageEngine = StorageEngine(
                storageAdapter: storageAdapter,
                dataStoreConfiguration: .testDefault(),
                syncEngine: syncEngine,
                validAPIPluginKey: validAPIPluginKey,
                validAuthPluginKey: validAuthPluginKey
            )
        } catch {
            XCTFail(String(describing: error))
            return
        }

        let storageEngineBehaviorFactory: StorageEngineBehaviorFactory = {_, _, _, _, _, _  throws in
            return storageEngine
        }
        let dataStorePublisher = DataStorePublisher()
        let dataStorePlugin = AWSDataStorePlugin(
            modelRegistration: TestModelRegistration(),
            storageEngineBehaviorFactory: storageEngineBehaviorFactory,
            dataStorePublisher: dataStorePublisher,
            validAPIPluginKey: validAPIPluginKey,
            validAuthPluginKey: validAuthPluginKey
        )

        let dataStoreConfig = DataStoreCategoryConfiguration(plugins: [
            "awsDataStorePlugin": true
        ])

        // Since these tests use syncable models, we have to set up an API category also
        let apiConfig = APICategoryConfiguration(plugins: ["MockAPICategoryPlugin": true])
        let apiPlugin = MockAPICategoryPlugin()

        let amplifyConfig = AmplifyConfiguration(api: apiConfig, dataStore: dataStoreConfig)

        do {
            try Amplify.add(plugin: apiPlugin)
            try Amplify.add(plugin: dataStorePlugin)
            try Amplify.configure(amplifyConfig)
        } catch {
            XCTFail(String(describing: error))
            return
        }
    }

    /// - Given: A configured Amplify system on iOS 13 or higher
    /// - When:
    ///    - I get an async sequence observing a model
    /// - Then:
    ///    - I receive notifications for updates to that model
    func testObserve() async throws {
        let receivedMutationEvent = expectation(description: "Received mutation event")
        receivedMutationEvent.assertForOverFulfill = false
        let subscription = Task {
            let mutationEvents = Amplify.DataStore.observe(Post.self)
            do {
                for try await _ in mutationEvents {
                    receivedMutationEvent.fulfill()
                }
            } catch {
                XCTFail("Unexpected error: \(error)")
            }
        }

        let model = Post(
            id: UUID().uuidString,
            title: "Test Post",
            content: "Test Post Content",
            createdAt: .now(),
            updatedAt: nil,
            draft: false,
            rating: nil,
            comments: []
        )

        _ = try await Amplify.DataStore.save(model)
        await fulfillment(of: [receivedMutationEvent], timeout: 1.0)
        subscription.cancel()
    }

    /// - Given: A configured DataStore
    /// - When:
    ///    - I subscribe to model events
    /// - Then:
    ///    - I am notified of `create` mutations
    func testCreate() async throws {
        let receivedMutationEvent = expectation(description: "Received mutation event")
        receivedMutationEvent.assertForOverFulfill = false
        let subscription = Task {
            let mutationEvents = Amplify.DataStore.observe(Post.self)
            do {
                for try await mutationEvent in mutationEvents {
                    if mutationEvent.mutationType == MutationEvent.MutationType.create.rawValue {
                        receivedMutationEvent.fulfill()
                    }
                }
            } catch {
                XCTFail("Unexpected error: \(error)")
            }
        }

        let model = Post(
            id: UUID().uuidString,
            title: "Test Post",
            content: "Test Post Content",
            createdAt: .now(),
            updatedAt: nil,
            draft: false,
            rating: nil,
            comments: []
        )

        _ = try await Amplify.DataStore.save(model)
        await fulfillment(of: [receivedMutationEvent], timeout: 1.0)

        subscription.cancel()
    }

    /// - Given: A configured DataStore
    /// - When:
    ///    - I subscribe to model events
    /// - Then:
    ///    - I am notified of `update` mutations
    func testUpdate() async throws {
        let originalContent = "Content as of \(Date())"
        let model = Post(
            id: UUID().uuidString,
            title: "Test Post",
            content: originalContent,
            createdAt: .now(),
            updatedAt: nil,
            draft: false,
            rating: nil,
            comments: []
        )

        _ = try await Amplify.DataStore.save(model)

        let newContent = "Updated content as of \(Date())"
        var newModel = model
        newModel.content = newContent
        newModel.updatedAt = .now()

        let receivedMutationEvent = expectation(description: "Received mutation event")
        receivedMutationEvent.assertForOverFulfill = false

        let subscription = Task {
            let mutationEvents = Amplify.DataStore.observe(Post.self)
            do {
                for try await _ in mutationEvents {
                    receivedMutationEvent.fulfill()
                }
            } catch {
                XCTFail("Unexpected error: \(error)")
            }
        }

        _ = try await Amplify.DataStore.save(newModel)

        await fulfillment(of: [receivedMutationEvent], timeout: 1.0)

        subscription.cancel()
    }

    /// - Given: A configured DataStore
    /// - When:
    ///    - I subscribe to model events
    /// - Then:
    ///    - I am notified of `delete` mutations
    func testDelete() async throws {
        let receivedMutationEvent = expectation(description: "Received mutation event")
        receivedMutationEvent.assertForOverFulfill = false

        let subscription = Task {
            let mutationEvents = Amplify.DataStore.observe(Post.self)
            do {
                for try await mutationEvent in mutationEvents {
                    if mutationEvent.mutationType == MutationEvent.MutationType.delete.rawValue {
                        receivedMutationEvent.fulfill()
                    }
                }
            } catch {
                XCTFail("Unexpected error: \(error)")
            }
        }

        let model = Post(
            title: "Test Post",
            content: "Test Post Content",
            createdAt: .now()
        )

        _ = try await Amplify.DataStore.save(model)
        _ = try await Amplify.DataStore.delete(model)
        await fulfillment(of: [receivedMutationEvent], timeout: 1.0)

        subscription.cancel()
    }

}
