//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Contacts
import LibSignalClient
import XCTest

@testable import SignalServiceKit

final class ContactStreamTest: XCTestCase {

    let outputContactSyncData = "GQoMKzEzMjMxMTExMTExEgdBbGljZS0xQAAxEgdBbGljZS0yQABKJDMxY2UxNDEyLTlhMjgtNGU2Zi1iNGVlLTIyMjIyMjIyMjIyMj8KDCsxMzIxMzMzMzMzMxIHQWxpY2UtM0AASiQxZDRhYjA0NS04OGZiLTRjNGUtOWY2YS0zMzMzMzMzMzMzMzM="

    private func makeAccount(phoneNumber: String?, serviceId: String?, fullName: String) -> SignalAccount {
        return SignalAccount(
            recipientPhoneNumber: phoneNumber,
            recipientServiceId: serviceId.map({ Aci.constantForTesting($0) }),
            multipleAccountLabelText: nil,
            cnContactId: nil,
            givenName: "",
            familyName: "",
            nickname: "",
            fullName: fullName,
            contactAvatarHash: nil
        )
    }

    func test_writeContactSync() throws {
        let signalAccounts = [
            makeAccount(phoneNumber: "+13231111111", serviceId: nil, fullName: "Alice-1"),
            makeAccount(phoneNumber: nil, serviceId: "31ce1412-9a28-4e6f-b4ee-222222222222", fullName: "Alice-2"),
            makeAccount(phoneNumber: "+13213333333", serviceId: "1d4ab045-88fb-4c4e-9f6a-333333333333", fullName: "Alice-3"),
        ]

        let streamData = try buildContactSyncData(signalAccounts: signalAccounts)

        XCTAssertEqual(streamData.base64EncodedString(), outputContactSyncData)
    }

    func test_readContactSync() throws {
        var contacts: [ContactDetails] = []

        let data = Data(base64Encoded: outputContactSyncData)!
        try data.withUnsafeBytes { bufferPtr in
            if let baseAddress = bufferPtr.baseAddress, bufferPtr.count > 0 {
                let pointer = baseAddress.assumingMemoryBound(to: UInt8.self)
                let inputStream = ChunkedInputStream(forReadingFrom: pointer, count: bufferPtr.count)
                let contactStream = ContactsInputStream(inputStream: inputStream)
                while let nextContact = try contactStream.decodeContact() {
                    contacts.append(nextContact)
                }
            }
        }

        guard contacts.count == 3 else {
            XCTFail("unexpected contact count: \(contacts.count)")
            return
        }

        do {
            let contact = contacts[0]
            XCTAssertEqual("+13231111111", contact.phoneNumber?.stringValue)
            XCTAssertNil(contact.aci)
            XCTAssertEqual(0, contact.expireTimer)
            XCTAssertNil(contact.inboxSortOrder)
        }

        do {
            let contact = contacts[1]
            XCTAssertNil(contact.phoneNumber)
            XCTAssertEqual("31CE1412-9A28-4E6F-B4EE-222222222222", contact.aci?.serviceIdUppercaseString)
            XCTAssertEqual(0, contact.expireTimer)
            XCTAssertNil(contact.inboxSortOrder)
        }

        do {
            let contact = contacts[2]
            XCTAssertEqual("+13213333333", contact.phoneNumber?.stringValue)
            XCTAssertEqual("1D4AB045-88FB-4C4E-9F6A-333333333333", contact.aci?.serviceIdUppercaseString)
            XCTAssertEqual(0, contact.expireTimer)
            XCTAssertNil(contact.inboxSortOrder)
        }
    }

    private func buildContactSyncData(signalAccounts: [SignalAccount]) throws -> Data {
        let dataOutputStream = OutputStream(toMemory: ())
        dataOutputStream.open()
        defer { dataOutputStream.close() }
        let contactOutputStream = ContactOutputStream(outputStream: dataOutputStream)

        for signalAccount in signalAccounts {
            try contactOutputStream.writeContact(
                aci: signalAccount.recipientServiceId as? Aci,
                phoneNumber: E164(signalAccount.recipientPhoneNumber),
                signalAccount: signalAccount,
                disappearingMessagesConfiguration: nil,
                inboxPosition: nil
            )
        }

        return dataOutputStream.property(forKey: .dataWrittenToMemoryStreamKey) as! Data
    }
}
