//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//

@testable import ElementX
import MatrixRustSDK
import Testing

@MainActor
struct NotificationSettingsEditScreenViewModelTests {
    private var viewModel: NotificationSettingsEditScreenViewModelProtocol!
    private var notificationSettingsProxy: NotificationSettingsProxyMock!
    private var userSession: UserSessionMock!
    private var clientProxy: ClientProxyMock!
    
    private var context: NotificationSettingsEditScreenViewModelType.Context {
        viewModel.context
    }
    
    init() throws {
        notificationSettingsProxy = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration())
        notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .allMessages
        
        clientProxy = ClientProxyMock(.init(userID: "@a:b.com", notificationSettings: notificationSettingsProxy))
        userSession = UserSessionMock(.init(clientProxy: clientProxy))
    }
    
    @Test
    mutating func fetchSettings() async throws {
        notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
            switch (isEncrypted, isOneToOne) {
            case (_, true):
                return .allMessages
            case (_, _):
                return .mentionsAndKeywordsOnly
            }
        }
        viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)

        let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
        
        viewModel.fetchInitialContent()
        
        try await deferred.fulfill()
        
        // `getDefaultRoomNotificationModeIsEncryptedIsOneToOne` must have been called twice (for encrypted and unencrypted group chats)
        let invocations = notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReceivedInvocations
        
        #expect(invocations.count == 2)
        // First call for encrypted group chats
        #expect(invocations[0].isEncrypted == true)
        #expect(invocations[0].isOneToOne == false)
        // Second call for unencrypted group chats
        #expect(invocations[1].isEncrypted == false)
        #expect(invocations[1].isOneToOne == false)
        
        #expect(context.viewState.defaultMode == .mentionsAndKeywordsOnly)
        #expect(context.viewState.bindings.alertInfo == nil)
        #expect(!context.viewState.canPushEncryptedEvents)
        #expect(context.viewState.description(for: .mentionsAndKeywordsOnly) != nil)
    }
    
    @Test
    mutating func fetchSettingsWithCanPushEncryptedEvents() async throws {
        notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
            switch (isEncrypted, isOneToOne) {
            case (_, true):
                return .allMessages
            case (_, _):
                return .mentionsAndKeywordsOnly
            }
        }
        notificationSettingsProxy.canPushEncryptedEventsToDeviceClosure = {
            true
        }
        viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)

        let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
        
        viewModel.fetchInitialContent()
        
        try await deferred.fulfill()
        
        // `getDefaultRoomNotificationModeIsEncryptedIsOneToOne` must have been called twice (for encrypted and unencrypted group chats)
        let invocations = notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReceivedInvocations
        
        #expect(invocations.count == 2)
        // First call for encrypted group chats
        #expect(invocations[0].isEncrypted == true)
        #expect(invocations[0].isOneToOne == false)
        // Second call for unencrypted group chats
        #expect(invocations[1].isEncrypted == false)
        #expect(invocations[1].isOneToOne == false)
        
        #expect(context.viewState.defaultMode == .mentionsAndKeywordsOnly)
        #expect(context.viewState.bindings.alertInfo == nil)
        #expect(context.viewState.canPushEncryptedEvents)
        #expect(context.viewState.description(for: .mentionsAndKeywordsOnly) == nil)
    }
    
    @Test
    mutating func setModeAllMessages() async throws {
        notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .mentionsAndKeywordsOnly
        viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)
        let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
        
        viewModel.fetchInitialContent()
        
        try await deferred.fulfill()
        
        var deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
                                                 transitionValues: [nil, .allMessages, nil])

        context.send(viewAction: .setMode(.allMessages))
        
        try await deferredViewState.fulfill()
        
        // `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted group chats)
        let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
        #expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 2)
        // First call for encrypted group chats
        #expect(invocations[0].isEncrypted == true)
        #expect(invocations[0].isOneToOne == false)
        #expect(invocations[0].mode == .allMessages)
        // Second call for unencrypted group chats
        #expect(invocations[1].isEncrypted == false)
        #expect(invocations[1].isOneToOne == false)
        #expect(invocations[1].mode == .allMessages)
        
        deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode),
                                             transitionValues: [.allMessages])
        
        try await deferredViewState.fulfill()

        #expect(context.viewState.defaultMode == .allMessages)
        #expect(context.viewState.bindings.alertInfo == nil)
    }

    @Test
    mutating func setModeMentions() async throws {
        viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)
        
        let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
        
        viewModel.fetchInitialContent()
        
        try await deferred.fulfill()
        
        var deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
                                                 transitionValues: [nil, .mentionsAndKeywordsOnly, nil])
                
        context.send(viewAction: .setMode(.mentionsAndKeywordsOnly))
        
        try await deferredViewState.fulfill()
        
        // `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted group chats)
        let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
        #expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 2)
        // First call for encrypted group chats
        #expect(invocations[0].isEncrypted == true)
        #expect(invocations[0].isOneToOne == false)
        #expect(invocations[0].mode == .mentionsAndKeywordsOnly)
        // Second call for unencrypted group chats
        #expect(invocations[1].isEncrypted == false)
        #expect(invocations[1].isOneToOne == false)
        #expect(invocations[1].mode == .mentionsAndKeywordsOnly)
        
        deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode),
                                             transitionValues: [.mentionsAndKeywordsOnly])
        
        try await deferredViewState.fulfill()

        #expect(context.viewState.defaultMode == .mentionsAndKeywordsOnly)
        #expect(context.viewState.bindings.alertInfo == nil)
    }

    @Test
    mutating func setModeDirectChats() async throws {
        notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .mentionsAndKeywordsOnly
        // Initialize for direct chats
        viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat, userSession: userSession)
        
        let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
        
        viewModel.fetchInitialContent()
        
        try await deferred.fulfill()
        
        let deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
                                                 transitionValues: [nil, .allMessages, nil])
        
        context.send(viewAction: .setMode(.allMessages))
        
        try await deferredViewState.fulfill()

        // `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted direct chats)
        let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
        #expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 2)
        // First call for encrypted direct chats
        #expect(invocations[0].isEncrypted == true)
        #expect(invocations[0].isOneToOne == true)
        #expect(invocations[0].mode == .allMessages)
        // Second call for unencrypted direct chats
        #expect(invocations[1].isEncrypted == false)
        #expect(invocations[1].isOneToOne == true)
        #expect(invocations[1].mode == .allMessages)
    }

    @Test
    mutating func setModeFailure() async throws {
        notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .mentionsAndKeywordsOnly
        notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeThrowableError = NotificationSettingsError.Generic(msg: "error")
        viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat, userSession: userSession)
        
        let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
        
        viewModel.fetchInitialContent()
        
        try await deferred.fulfill()
        
        let deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
                                                 transitionValues: [nil, .allMessages, nil])

        context.send(viewAction: .setMode(.allMessages))
        
        try await deferredViewState.fulfill()
        
        #expect(context.viewState.bindings.alertInfo != nil)
    }

    @Test
    mutating func selectRoom() async throws {
        let roomID = "!roomidentifier:matrix.org"
        viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat, userSession: userSession)
        
        let deferredActions = deferFulfillment(viewModel.actions) { action in
            switch action {
            case .requestRoomNotificationSettingsPresentation:
                return true
            }
        }
        
        context.send(viewAction: .selectRoom(roomIdentifier: roomID))
        
        let sentAction = try await deferredActions.fulfill()
        
        let expectedAction = NotificationSettingsEditScreenViewModelAction.requestRoomNotificationSettingsPresentation(roomID: roomID)
        guard case let .requestRoomNotificationSettingsPresentation(roomID: receivedRoomID) = sentAction, receivedRoomID == roomID else {
            Issue.record("Expected action \(expectedAction), but was \(sentAction)")
            return
        }
    }
}
