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

import AWSPluginsCore
import Combine
import XCTest

@testable import Amplify
@testable import AWSDataStorePlugin

class MockSQLiteStorageEngineAdapter: StorageEngineAdapter {

    static let maxNumberOfPredicates: Int = 950

    var responders = [ResponderKeys: Any]()

    var resultForQuery: DataStoreResult<[Model]>?
    var resultForSave: DataStoreResult<Model>?

    var resultForQueryMutationSyncMetadata: MutationSyncMetadata?
    var resultForQueryMutationSyncMetadatas: [MutationSyncMetadata]
    var errorToThrowOnMutationSyncMetadata: DataStoreError?
    var errorToThrowOnTransaction: Error?

    var shouldReturnErrorOnSaveMetadata: Bool
    var shouldReturnErrorOnDeleteMutation: Bool
    var shouldIgnoreError: Bool

    var resultForQueryModelSyncMetadata: ModelSyncMetadata?
    var listenerForModelSyncMetadata: BasicClosure?

    init() {
        self.shouldReturnErrorOnSaveMetadata = false
        self.shouldReturnErrorOnDeleteMutation = false
        self.shouldIgnoreError = false
        self.resultForQueryMutationSyncMetadatas = [MutationSyncMetadata]()
    }

    func setUp(modelSchemas: [ModelSchema]) throws {
        XCTFail("Not expected to execute")
    }

    func applyModelMigrations(modelSchemas: [ModelSchema]) throws {
        XCTFail("Not expected to execute")
    }

    // MARK: - Responses

    func returnOnQuery(dataStoreResult: DataStoreResult<[Model]>?) {
        resultForQuery = dataStoreResult
    }

    func returnOnQueryMutationSyncMetadata(_ mutationSyncMetadata: MutationSyncMetadata?) {
        resultForQueryMutationSyncMetadata = mutationSyncMetadata
    }

    func returnOnQueryMutationSyncMetadatas(_ mutationSyncMetadatas: [MutationSyncMetadata]) {
        resultForQueryMutationSyncMetadatas = mutationSyncMetadatas
    }

    func returnOnSave(dataStoreResult: DataStoreResult<Model>?) {
        resultForSave = dataStoreResult
    }

    func returnOnQueryModelSyncMetadata(_ metadata: ModelSyncMetadata?, listener: BasicClosure? = nil) {
        resultForQueryModelSyncMetadata = metadata
        listenerForModelSyncMetadata = listener
    }

    func throwOnQueryMutationSyncMetadata(error: DataStoreError) {
        errorToThrowOnMutationSyncMetadata = error
    }

    func throwOnTransaction(error: DataStoreError) {
        errorToThrowOnTransaction = error
    }

    // MARK: - StorageEngineAdapter

    func delete<M: Model>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        withId id: String,
        condition: QueryPredicate?,
        completion: @escaping DataStoreCallback<M?>
    ) {
        XCTFail("Not expected to execute")
    }

    func delete<M: Model>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        filter: QueryPredicate,
        completion: @escaping DataStoreCallback<[M]>
    ) {
        XCTFail("Not expected to execute")
    }
    func delete<M>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        withIdentifier identifier: ModelIdentifierProtocol,
        condition: QueryPredicate?,
        completion: @escaping DataStoreCallback<M?>
    ) where M: Model {
        XCTFail("Not expected to execute")
    }

    func delete(
        untypedModelType modelType: Model.Type,
        modelSchema: ModelSchema,
        withIdentifier identifier: ModelIdentifierProtocol,
        condition: QueryPredicate?,
        completion: (DataStoreResult<Void>) -> Void
    ) {
        if let responder = responders[.deleteUntypedModel] as? DeleteUntypedModelCompletionResponder {
            let result = responder.callback((modelType, identifier.stringValue))
            completion(result)
            return
        }

        return shouldReturnErrorOnDeleteMutation
        ? completion(.failure(causedBy: DataStoreError.invalidModelName("DelMutate")))
        : completion(.emptyResult)
    }

    func query(
        modelSchema: ModelSchema,
        predicate: QueryPredicate?,
        eagerLoad: Bool,
        completion: DataStoreCallback<[Model]>
    ) {
        let result = resultForQuery ?? .failure(DataStoreError.invalidOperation(causedBy: nil))
        completion(result)
    }

    func query<M: Model>(
        _ modelType: M.Type,
        predicate: QueryPredicate?,
        paginationInput: QueryPaginationInput?,
        eagerLoad: Bool,
        completion: DataStoreCallback<[M]>
    ) {
        XCTFail("Not expected to execute")
    }

    func query<M: Model>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        predicate: QueryPredicate?,
        sort: [QuerySortDescriptor]?,
        paginationInput: QueryPaginationInput?,
        eagerLoad: Bool,
        completion: (DataStoreResult<[M]>) -> Void
    ) {
        XCTFail("Not expected to execute")
    }

    func queryMutationSync(for models: [Model], modelName: String) throws -> [MutationSync<AnyModel>] {
        XCTFail("Not expected to execute")
        return []
    }

    func exists(_ modelSchema: ModelSchema, withIdentifier id: ModelIdentifierProtocol, predicate: QueryPredicate?) throws -> Bool {
        XCTFail("Not expected to execute")
        return true
    }

    func save(untypedModel: Model, eagerLoad: Bool, completion: @escaping DataStoreCallback<Model>) {
        if let responder = responders[.saveUntypedModel] as? SaveUntypedModelResponder {
            responder.callback((untypedModel, completion))
            return
        }

        completion(resultForSave!)
    }

    func save<M: Model>(
        _ model: M,
        condition: QueryPredicate?,
        eagerLoad: Bool,
        completion: @escaping DataStoreCallback<M>
    ) {
        if let responder = responders[.saveModelCompletion] as? SaveModelCompletionResponder<M> {
            responder.callback((model, completion))
            return
        }

        return shouldReturnErrorOnSaveMetadata
            ? completion(.failure(DataStoreError.invalidModelName("forceError")))
            : completion(.success(model))
    }

    func save<M: Model>(
        _ model: M,
        modelSchema: ModelSchema,
        condition: QueryPredicate?,
        eagerLoad: Bool
    ) -> DataStoreResult<M> {
        XCTFail("Not yet implemented")
        return .failure(.internalOperation("", "", nil))
    }

    func save<M: Model>(
        _ model: M,
        modelSchema: ModelSchema,
        condition where: QueryPredicate?,
        eagerLoad: Bool,
        completion: @escaping DataStoreCallback<M>
    ) {
        if let responder = responders[.saveModelCompletion] as? SaveModelCompletionResponder<M> {
            responder.callback((model, completion))
            return
        }

        return shouldReturnErrorOnSaveMetadata
            ? completion(.failure(DataStoreError.invalidModelName("forceError")))
            : completion(.success(model))
    }

    func queryMutationSync(forAnyModel anyModel: AnyModel) throws -> MutationSync<AnyModel>? {
        XCTFail("Not expected to execute")
        return nil
    }

    func query<M: Model>(
        _ modelType: M.Type,
        predicate: QueryPredicate?,
        sort: [QuerySortDescriptor]?,
        paginationInput: QueryPaginationInput?,
        eagerLoad: Bool,
        completion: DataStoreCallback<[M]>
    ) {
        if let responder = responders[.queryModelTypePredicate]
            as? QueryModelTypePredicateResponder<M> {
            let result = responder.callback((modelType, predicate))
            completion(result)
            return
        }

        completion(.success([]))
    }

    func queryMutationSyncMetadata(for modelId: String, modelName: String) throws -> MutationSyncMetadata? {
        if let responder = responders[.queryMutationSyncMetadata] as? QueryMutationSyncMetadataResponder {
            return try responder.callback(modelId)
        }

        if let err = errorToThrowOnMutationSyncMetadata {
            errorToThrowOnMutationSyncMetadata = nil
            throw err
        }
        return resultForQueryMutationSyncMetadata
    }

    func queryMutationSyncMetadata(for modelIds: [String], modelName: String) throws -> [MutationSyncMetadata] {
        if let responder = responders[.queryMutationSyncMetadatas] as? QueryMutationSyncMetadatasResponder {
            return try responder.callback(modelIds)
        }

        if let err = errorToThrowOnMutationSyncMetadata {
            errorToThrowOnMutationSyncMetadata = nil
            throw err
        }
        return resultForQueryMutationSyncMetadatas
    }

    func queryModelSyncMetadata(for modelSchema: ModelSchema) throws -> ModelSyncMetadata? {
        listenerForModelSyncMetadata?()
        return resultForQueryModelSyncMetadata
    }

    func transaction(_ basicClosure: () throws -> Void) throws {
        if let err = errorToThrowOnTransaction {
            errorToThrowOnTransaction = nil
            throw err
        }
        try basicClosure()
    }

    func clear(completion: @escaping DataStoreCallback<Void>) {

    }

    func shouldIgnoreError(error: DataStoreError) -> Bool {
        return shouldIgnoreError
    }

    func removeStore(for modelSchema: ModelSchema) throws -> String {
        XCTFail("Not expected to execute")
        return ""
    }

    func createStore(for modelSchema: ModelSchema) throws -> String {
        XCTFail("Not expected to execute")
        return ""
    }

    func emptyStore(for modelSchema: ModelSchema) throws -> String {
        XCTFail("Not expected to execute")
        return ""
    }

    func renameStore(from: ModelSchema, toModelSchema: ModelSchema) throws -> String {
        XCTFail("Not expected to execute")
        return ""
    }
}

class MockStorageEngineBehavior: StorageEngineBehavior {

    static let mockStorageEngineBehaviorFactory =
        MockStorageEngineBehavior.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:)
    var responders = [ResponderKeys: Any]()
    var syncing = false

    init() {
    }

    init(
        isSyncEnabled: Bool,
        dataStoreConfiguration: DataStoreConfiguration,
        validAPIPluginKey: String = "awsAPIPlugin",
        validAuthPluginKey: String = "awsCognitoAuthPlugin",
        modelRegistryVersion: String,
        userDefault: UserDefaults = UserDefaults.standard
    ) throws {
    }

    func setupPublisher() {

    }

    var syncsFromRemote: Bool { true }

    var mockSyncEnginePublisher: PassthroughSubject<RemoteSyncEngineEvent, DataStoreError>!
    var mockSyncEngineSubscription: AnyCancellable! {
        willSet {
            if let subscription = mockSyncEngineSubscription {
                subscription.cancel()
            }
        }
    }

    var mockPublisher = PassthroughSubject<StorageEngineEvent, DataStoreError>()
    var publisher: AnyPublisher<StorageEngineEvent, DataStoreError> {
        mockPublisher.eraseToAnyPublisher()
    }

    func startSync() -> Swift.Result<SyncEngineInitResult, DataStoreError> {
        if !syncing {
            if let responder = responders[.startSync] as? StartSyncResponder {
                responder.callback("")
            }
            syncing = true
            mockSyncEnginePublisher = PassthroughSubject()
            mockSyncEngineSubscription = mockSyncEnginePublisher.sink(receiveCompletion: { completion in
                self.stopSync(completion: { _ in })
            }, receiveValue: { _ in })
            return .success(.successfullyInitialized)
        } else {
            return .success(.alreadyInitialized)
        }
    }

    func stopSync(completion: @escaping DataStoreCallback<Void>) {
        if let responder = responders[.stopSync] as? StopSyncResponder {
            responder.callback("")
        }
        syncing = false
        completion(.successfulVoid)
    }

    func setUp(modelSchemas: [ModelSchema]) throws {
    }

    func applyModelMigrations(modelSchemas: [ModelSchema]) throws {
    }

    func save<M: Model>(
        _ model: M,
        condition: QueryPredicate?,
        eagerLoad: Bool,
        completion: @escaping DataStoreCallback<M>
    ) {
        XCTFail("Not expected to execute")
    }

    func save<M: Model>(
        _ model: M,
        modelSchema: ModelSchema,
        condition where: QueryPredicate?,
        eagerLoad: Bool,
        completion: @escaping DataStoreCallback<M>
    ) {
        XCTFail("Not expected to execute")
    }

    func delete<M: Model>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        withId id: String,
        condition: QueryPredicate?,
        completion: @escaping DataStoreCallback<M?>
    ) {
        XCTFail("Not expected to execute")
    }

    func delete<M: Model>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        filter: QueryPredicate,
        completion: @escaping DataStoreCallback<[M]>
    ) {
        XCTFail("Not expected to execute")
    }

    func delete<M>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        withIdentifier identifier: ModelIdentifierProtocol,
        condition: QueryPredicate?,
        completion: @escaping DataStoreCallback<M?>
    ) where M: Model {
        completion(.success(nil))
    }

    func query<M: Model>(
        _ modelType: M.Type,
        predicate: QueryPredicate?,
        sort: [QuerySortDescriptor]?,
        paginationInput: QueryPaginationInput?,
        eagerLoad: Bool,
        completion: DataStoreCallback<[M]>
    ) {
        if let responder = responders[.query] as? QueryResponder<M> {
            let result = responder.callback(())
            completion(result)
        } else {
            completion(.success([]))
        }
    }

    func query<M: Model>(
        _ modelType: M.Type,
        modelSchema: ModelSchema,
        predicate: QueryPredicate?,
        sort: [QuerySortDescriptor]?,
        paginationInput: QueryPaginationInput?,
        eagerLoad: Bool,
        completion: (DataStoreResult<[M]>) -> Void
    ) {

        if let responder = responders[.query] as? QueryResponder<M> {
            let result = responder.callback(())
            completion(result)
        } else {
            completion(.success([]))
        }
    }

    func clear(completion: @escaping DataStoreCallback<Void>) {
        syncing = false
        completion(.successfulVoid)
        if let responder = responders[.clear] as? ClearResponder {
            return responder.callback("")
        }
    }
}
