// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.

import Foundation

import Quick
import Nimble
import SessionUtilitiesKit

@testable import SessionMessagingKit

class SOGSMessageSpec: QuickSpec {
    override class func spec() {
        // MARK: Configuration
        
        @TestState var messageJson: String! = """
        {
            "id": 123,
            "session_id": "05\(TestConstants.publicKey)",
            "posted": 234,
            "seqno": 345,
            "whisper": false,
            "whisper_mods": false,
                    
            "data": "VGVzdERhdGE=",
            "signature": "VGVzdFNpZ25hdHVyZQ=="
        }
        """
        @TestState var messageData: Data! = messageJson.data(using: .utf8)!
        @TestState var mockCrypto: MockCrypto! = MockCrypto()
        @TestState var dependencies: Dependencies! = Dependencies(
            crypto: mockCrypto
        )
        @TestState var decoder: JSONDecoder! = {
            let result = JSONDecoder()
            result.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
            return result
        }()
        
        // MARK: - a SOGSMessage
        describe("a SOGSMessage") {
            // MARK: -- when decoding
            context("when decoding") {
                // MARK: ---- defaults the whisper values to false
                it("defaults the whisper values to false") {
                    messageJson = """
                    {
                        "id": 123,
                        "posted": 234,
                        "seqno": 345
                    }
                    """
                    messageData = messageJson.data(using: .utf8)!
                    let result: OpenGroupAPI.Message? = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                    
                    expect(result).toNot(beNil())
                    expect(result?.whisper).to(beFalse())
                    expect(result?.whisperMods).to(beFalse())
                }
                
                // MARK: ---- and there is no content
                context("and there is no content") {
                    // MARK: ------ does not need a sender
                    it("does not need a sender") {
                        messageJson = """
                        {
                            "id": 123,
                            "posted": 234,
                            "seqno": 345,
                            "whisper": false,
                            "whisper_mods": false
                        }
                        """
                        messageData = messageJson.data(using: .utf8)!
                        let result: OpenGroupAPI.Message? = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                        
                        expect(result).toNot(beNil())
                        expect(result?.sender).to(beNil())
                        expect(result?.base64EncodedData).to(beNil())
                        expect(result?.base64EncodedSignature).to(beNil())
                    }
                }
                
                // MARK: ---- and there is content
                context("and there is content") {
                    // MARK: ------ errors if there is no sender
                    it("errors if there is no sender") {
                        messageJson = """
                        {
                            "id": 123,
                            "posted": 234,
                            "seqno": 345,
                            "whisper": false,
                            "whisper_mods": false,
                        
                            "data": "VGVzdERhdGE=",
                            "signature": "VGVzdFNpZ25hdHVyZQ=="
                        }
                        """
                        messageData = messageJson.data(using: .utf8)!
                        
                        expect {
                            try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                        }
                        .to(throwError(HTTPError.parsingFailed))
                    }
                    
                    // MARK: ------ errors if the data is not a base64 encoded string
                    it("errors if the data is not a base64 encoded string") {
                        messageJson = """
                        {
                            "id": 123,
                            "session_id": "05\(TestConstants.publicKey)",
                            "posted": 234,
                            "seqno": 345,
                            "whisper": false,
                            "whisper_mods": false,
                        
                            "data": "Test!!!",
                            "signature": "VGVzdFNpZ25hdHVyZQ=="
                        }
                        """
                        messageData = messageJson.data(using: .utf8)!
                        
                        expect {
                            try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                        }
                        .to(throwError(HTTPError.parsingFailed))
                    }
                    
                    // MARK: ------ errors if the signature is not a base64 encoded string
                    it("errors if the signature is not a base64 encoded string") {
                        messageJson = """
                        {
                            "id": 123,
                            "session_id": "05\(TestConstants.publicKey)",
                            "posted": 234,
                            "seqno": 345,
                            "whisper": false,
                            "whisper_mods": false,
                        
                            "data": "VGVzdERhdGE=",
                            "signature": "Test!!!"
                        }
                        """
                        messageData = messageJson.data(using: .utf8)!
                        
                        expect {
                            try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                        }
                        .to(throwError(HTTPError.parsingFailed))
                    }
                    
                    // MARK: ------ errors if the dependencies are not provided to the JSONDecoder
                    it("errors if the dependencies are not provided to the JSONDecoder") {
                        decoder = JSONDecoder()
                        
                        expect {
                            try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                        }
                        .to(throwError(HTTPError.parsingFailed))
                    }
                    
                    // MARK: ------ errors if the session_id value is not valid
                    it("errors if the session_id value is not valid") {
                        messageJson = """
                        {
                            "id": 123,
                            "session_id": "TestId",
                            "posted": 234,
                            "seqno": 345,
                            "whisper": false,
                            "whisper_mods": false,
                        
                            "data": "VGVzdERhdGE=",
                            "signature": "VGVzdFNpZ25hdHVyZQ=="
                        }
                        """
                        messageData = messageJson.data(using: .utf8)!
                        
                        expect {
                            try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                        }
                        .to(throwError(HTTPError.parsingFailed))
                    }
                    
                    // MARK: ------ that is blinded
                    context("that is blinded") {
                        beforeEach {
                            messageJson = """
                            {
                                "id": 123,
                                "session_id": "15\(TestConstants.publicKey)",
                                "posted": 234,
                                "seqno": 345,
                                "whisper": false,
                                "whisper_mods": false,
                                        
                                "data": "VGVzdERhdGE=",
                                "signature": "VGVzdFNpZ25hdHVyZQ=="
                            }
                            """
                            messageData = messageJson.data(using: .utf8)!
                        }
                        
                        // MARK: -------- succeeds if it succeeds verification
                        it("succeeds if it succeeds verification") {
                            mockCrypto
                                .when {
                                    $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
                                }
                                .thenReturn(true)
                            
                            expect {
                                try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                            }
                            .toNot(beNil())
                        }
                        
                        // MARK: -------- provides the correct values as parameters
                        it("provides the correct values as parameters") {
                            mockCrypto
                                .when {
                                    $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
                                }
                                .thenReturn(true)
                            
                            _ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                            
                            expect(mockCrypto)
                                .to(call(matchingParameters: true) {
                                    $0.verify(
                                        .signature(
                                            message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
                                            publicKey: Data(hex: TestConstants.publicKey).bytes,
                                            signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
                                        )
                                    )
                                })
                        }
                        
                        // MARK: -------- throws if it fails verification
                        it("throws if it fails verification") {
                            mockCrypto
                                .when {
                                    $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
                                }
                                .thenReturn(false)
                            
                            expect {
                                try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                            }
                            .to(throwError(HTTPError.parsingFailed))
                        }
                    }
                    
                    // MARK: ------ that is unblinded
                    context("that is unblinded") {
                        // MARK: -------- succeeds if it succeeds verification
                        it("succeeds if it succeeds verification") {
                            mockCrypto
                                .when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
                                .thenReturn(true)
                            
                            expect {
                                try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                            }
                            .toNot(beNil())
                        }
                        
                        // MARK: -------- provides the correct values as parameters
                        it("provides the correct values as parameters") {
                            mockCrypto
                                .when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
                                .thenReturn(true)
                            
                            _ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                            
                            expect(mockCrypto)
                                .to(call(matchingParameters: true) {
                                    $0.verify(
                                        .signatureEd25519(
                                            Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
                                            publicKey: Data(hex: TestConstants.publicKey),
                                            data: Data(base64Encoded: "VGVzdERhdGE=")!
                                        )
                                    )
                                })
                        }
                        
                        // MARK: -------- throws if it fails verification
                        it("throws if it fails verification") {
                            mockCrypto
                                .when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
                                .thenReturn(false)
                            
                            expect {
                                try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
                            }
                            .to(throwError(HTTPError.parsingFailed))
                        }
                    }
                }
            }
        }
    }
}
