//
// Copyright 2022-2024 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.
//

import Combine
import XCTest

@testable import ElementX

@MainActor
class SessionVerificationViewModelTests: XCTestCase {
    var viewModel: SessionVerificationScreenViewModelProtocol!
    var context: SessionVerificationViewModelType.Context!
    var sessionVerificationController: SessionVerificationControllerProxyMock!
    
    override func setUpWithError() throws {
        sessionVerificationController = SessionVerificationControllerProxyMock.configureMock()
        viewModel = SessionVerificationScreenViewModel(sessionVerificationControllerProxy: sessionVerificationController,
                                                       flow: .deviceInitiator,
                                                       appSettings: AppSettings(),
                                                       mediaProvider: MediaProviderMock(configuration: .init()))
        context = viewModel.context
    }

    func testRequestVerification() async throws {
        XCTAssertEqual(context.viewState.verificationState, .initial)
        
        context.send(viewAction: .requestVerification)
        
        try await Task.sleep(for: .milliseconds(100))
        XCTAssert(sessionVerificationController.requestDeviceVerificationCallsCount == 1)
        XCTAssertEqual(context.viewState.verificationState, .requestingVerification)
    }
    
    func testVerificationCancellation() async throws {
        XCTAssertEqual(context.viewState.verificationState, .initial)
        
        context.send(viewAction: .requestVerification)
        
        viewModel.stop()
        
        XCTAssertEqual(context.viewState.verificationState, .cancelling)
        
        let deferred = deferFulfillment(context.$viewState) { state in
            state.verificationState == .cancelled
        }
        
        try await deferred.fulfill()
        
        XCTAssertEqual(context.viewState.verificationState, .cancelled)
        
        context.send(viewAction: .restart)
        
        XCTAssertEqual(context.viewState.verificationState, .initial)

        XCTAssert(sessionVerificationController.requestDeviceVerificationCallsCount == 1)
        XCTAssert(sessionVerificationController.cancelVerificationCallsCount == 1)
    }
    
    func testReceiveChallenge() {
        setupChallengeReceived()
    }
    
    func testAcceptChallenge() {
        setupChallengeReceived()
        
        let waitForAcceptance = XCTestExpectation(description: "Wait for acceptance")
        
        let cancellable = sessionVerificationController.actions
            .delay(for: .seconds(0.1), scheduler: DispatchQueue.main) // Allow the view model to process the callback first.
            .sink { callback in
                switch callback {
                case .finished:
                    waitForAcceptance.fulfill()
                default:
                    XCTFail("Unexpected session verification controller callback")
                }
            }
        
        defer {
            cancellable.cancel()
        }
                
        context.send(viewAction: .accept)
        
        wait(for: [waitForAcceptance], timeout: 10.0)
        
        XCTAssertEqual(context.viewState.verificationState, .verified)
        XCTAssert(sessionVerificationController.approveVerificationCallsCount == 1)
    }
    
    func testDeclineChallenge() {
        setupChallengeReceived()
        
        let expectation = XCTestExpectation(description: "Wait for cancellation")
        
        let cancellable = sessionVerificationController.actions
            .delay(for: .seconds(0.1), scheduler: DispatchQueue.main) // Allow the view model to process the callback first.
            .sink { callback in
                switch callback {
                case .cancelled:
                    expectation.fulfill()
                default:
                    XCTFail("Unexpected session verification controller callback")
                }
            }
        
        defer {
            cancellable.cancel()
        }
                
        context.send(viewAction: .decline)
        
        wait(for: [expectation], timeout: 10.0)
        
        XCTAssertEqual(context.viewState.verificationState, .cancelled)
        XCTAssert(sessionVerificationController.declineVerificationCallsCount == 1)
    }
    
    // MARK: - Private
    
    private func setupChallengeReceived() {
        let requestAcceptanceExpectation = XCTestExpectation(description: "Wait for request acceptance")
        let sasVerificationStartExpectation = XCTestExpectation(description: "Wait for SaS verification start")
        let verificationDataReceivalExpectation = XCTestExpectation(description: "Wait for Emoji data")
        
        let cancellable = sessionVerificationController.actions
            .delay(for: .seconds(0.1), scheduler: DispatchQueue.main) // Allow the view model to process the callback first.
            .sink { callback in
                switch callback {
                case .acceptedVerificationRequest:
                    requestAcceptanceExpectation.fulfill()
                case .startedSasVerification:
                    sasVerificationStartExpectation.fulfill()
                case .receivedVerificationData:
                    verificationDataReceivalExpectation.fulfill()
                default:
                    break
                }
            }
        
        defer {
            cancellable.cancel()
        }
        
        context.send(viewAction: .requestVerification)
        wait(for: [requestAcceptanceExpectation], timeout: 10.0)
        XCTAssertEqual(context.viewState.verificationState, .verificationRequestAccepted)
        
        context.send(viewAction: .startSasVerification)
        wait(for: [sasVerificationStartExpectation], timeout: 10.0)
        XCTAssertEqual(context.viewState.verificationState, .sasVerificationStarted)
        
        wait(for: [verificationDataReceivalExpectation], timeout: 10.0)
        XCTAssertEqual(context.viewState.verificationState, .showingChallenge(emojis: SessionVerificationControllerProxyMock.emojis))

        XCTAssert(sessionVerificationController.requestDeviceVerificationCallsCount == 1)
        XCTAssert(sessionVerificationController.startSasVerificationCallsCount == 1)
    }
}
