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

@testable import SignalServiceKit

import LibSignalClient
import XCTest

final class TSMessageStorageTest: SSKBaseTest {
    private var thread: TSContactThread!

    var localAci: Aci { return Aci.parseFrom(aciString: "00000000-0000-4000-8000-000000000000")! }
    var localAddress: SignalServiceAddress { return SignalServiceAddress(localAci) }

    var otherAci: Aci { return Aci.parseFrom(aciString: "00000000-0000-4000-8000-000000000001")! }
    var otherAddress: SignalServiceAddress { return SignalServiceAddress(otherAci) }

    private func numberOfInteractions(thread: TSThread, tx: SDSAnyReadTransaction) -> UInt {
        var count: UInt = 0
        try! InteractionFinder(threadUniqueId: thread.uniqueId)
            .enumerateInteractionIds(transaction: tx) { _, _ in
                count += 1
            }
        return count
    }

    override func setUp() {
        super.setUp()

        write { tx in
            (DependenciesBridge.shared.registrationStateChangeManager as! RegistrationStateChangeManagerImpl).registerForTests(
                localIdentifiers: .forUnitTests,
                tx: tx.asV2Write
            )

            self.thread = TSContactThread.getOrCreateThread(
                withContactAddress: self.otherAddress,
                transaction: tx
            )
        }
    }

    func testStoreIncomingMessage() {
        write { tx in
            let timestamp: UInt64 = 42
            let body = "So long, and thanks for all the fish!"

            let newMessage: TSIncomingMessage = TSIncomingMessageBuilder
                .withDefaults(
                    thread: thread,
                    timestamp: timestamp,
                    authorAci: otherAci,
                    messageBody: body
                ).build()
            newMessage.anyInsert(transaction: tx)

            guard let fetchedMessage = TSIncomingMessage.anyFetchIncomingMessage(
                uniqueId: newMessage.uniqueId,
                transaction: tx
            ) else {
                XCTFail("Failed to find inserted message!")
                return
            }

            XCTAssertEqual(body, fetchedMessage.body)
            XCTAssertEqual(timestamp, fetchedMessage.timestamp)
            XCTAssertFalse(fetchedMessage.wasRead)
            XCTAssertEqual(thread.uniqueId, fetchedMessage.uniqueThreadId)
        }
    }

    func testMessagesDeletedOnThreadDeletion() {
        write { tx -> Void in
            let body = "So long, and thanks for all the fish!"

            let messages = (0..<10).map { idx -> TSIncomingMessage in
                let newMessage: TSIncomingMessage = TSIncomingMessageBuilder
                    .withDefaults(
                        thread: thread,
                        timestamp: UInt64(idx) + 1,
                        authorAci: otherAci,
                        messageBody: body
                    ).build()
                newMessage.anyInsert(transaction: tx)
                return newMessage
            }

            for (idx, message) in messages.enumerated() {
                guard let fetchedMessage = TSIncomingMessage.anyFetchIncomingMessage(
                    uniqueId: message.uniqueId,
                    transaction: tx
                ) else {
                    XCTFail("Failed to find inserted message!")
                    return
                }

                XCTAssertEqual(body, fetchedMessage.body)
                XCTAssertEqual(message.uniqueId, fetchedMessage.uniqueId)
                XCTAssertEqual(UInt64(idx + 1), fetchedMessage.timestamp)
                XCTAssertFalse(fetchedMessage.wasRead)
                XCTAssertEqual(thread.uniqueId, fetchedMessage.uniqueThreadId)
            }

            DependenciesBridge.shared.threadSoftDeleteManager
                .softDelete(threads: [thread], sendDeleteForMeSyncMessage: false, tx: tx.asV2Write)

            for message in messages {
                XCTAssertNil(TSIncomingMessage.anyFetchIncomingMessage(
                    uniqueId: message.uniqueId,
                    transaction: tx
                ))
            }

            XCTAssertEqual(0, numberOfInteractions(thread: thread, tx: tx))
        }
    }

    func testGroupMessagesDeletedOnThreadDeletion() {
        write { tx in
            let body = "So long, and thanks for all the fish!"

            let groupThread = try! GroupManager.createGroupForTests(members: [
                localAddress,
                otherAddress
            ], transaction: tx)

            let messages = (0..<10).map { idx -> TSIncomingMessage in
                let authorIdx = idx % groupThread.groupModel.groupMembers.count
                let authorAddress = groupThread.groupModel.groupMembers[authorIdx]
                let newMessage: TSIncomingMessage = TSIncomingMessageBuilder
                    .withDefaults(
                        thread: groupThread,
                        timestamp: UInt64(idx + 1),
                        authorAci: authorAddress.aci!,
                        messageBody: body
                    ).build()
                newMessage.anyInsert(transaction: tx)
                return newMessage
            }

            for (idx, message) in messages.enumerated() {
                guard let fetchedMessage = TSIncomingMessage.anyFetchIncomingMessage(
                    uniqueId: message.uniqueId,
                    transaction: tx
                ) else {
                    XCTFail("Failed to find inserted message!")
                    return
                }

                XCTAssertEqual(body, fetchedMessage.body)
                XCTAssertEqual(message.uniqueId, fetchedMessage.uniqueId)
                XCTAssertEqual(UInt64(idx + 1), fetchedMessage.timestamp)
                XCTAssertFalse(fetchedMessage.wasRead)
                XCTAssertEqual(groupThread.uniqueId, fetchedMessage.uniqueThreadId)
            }

            DependenciesBridge.shared.threadSoftDeleteManager
                .softDelete(threads: [groupThread], sendDeleteForMeSyncMessage: false, tx: tx.asV2Write)

            for message in messages {
                XCTAssertNil(TSIncomingMessage.anyFetchIncomingMessage(
                    uniqueId: message.uniqueId,
                    transaction: tx
                ))
            }

            XCTAssertEqual(0, numberOfInteractions(thread: groupThread, tx: tx))
        }
    }
}
