import XCTest
@testable import Rosetta

@MainActor
final class BehaviorParityFixtureTests: XCTestCase {
    private var ctx: DBTestContext!

    override func setUpWithError() throws {
        ctx = DBTestContext()
    }

    override func tearDownWithError() throws {
        ctx.teardown()
        ctx = nil
    }

    func testIncomingDirectFixture() async throws {
        try await ctx.bootstrap()

        try await ctx.runScenario(FixtureScenario(name: "incoming direct", events: [
            .incoming(opponent: "02peer_direct", messageId: "in-1", timestamp: 100, text: "hello"),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        XCTAssertEqual(snapshot.messages.count, 1)
        XCTAssertEqual(snapshot.messages.first?.messageId, "in-1")
        XCTAssertEqual(snapshot.messages.first?.delivered, DeliveryStatus.delivered.rawValue)
        XCTAssertEqual(snapshot.messages.first?.read, false)

        XCTAssertEqual(snapshot.dialogs.count, 1)
        XCTAssertEqual(snapshot.dialogs.first?.opponentKey, "02peer_direct")
        XCTAssertEqual(snapshot.dialogs.first?.unreadCount, 1)
        XCTAssertEqual(snapshot.dialogs.first?.iHaveSent, false)
        XCTAssertEqual(snapshot.dialogs.first?.isRequest, true)
    }

    func testOutgoingDeliveredReadFixture() async throws {
        try await ctx.bootstrap()

        try await ctx.runScenario(FixtureScenario(name: "outgoing delivered read", events: [
            .outgoing(opponent: "02peer_ack", messageId: "out-1", timestamp: 200, text: "yo"),
            .markDelivered(opponent: "02peer_ack", messageId: "out-1"),
            .markOutgoingRead(opponent: "02peer_ack"),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        XCTAssertEqual(snapshot.messages.count, 1)
        XCTAssertEqual(snapshot.messages.first?.fromMe, true)
        XCTAssertEqual(snapshot.messages.first?.delivered, DeliveryStatus.delivered.rawValue)
        XCTAssertEqual(snapshot.messages.first?.read, true)

        XCTAssertEqual(snapshot.dialogs.first?.lastMessageFromMe, true)
        XCTAssertEqual(snapshot.dialogs.first?.lastMessageDelivered, DeliveryStatus.delivered.rawValue)
        XCTAssertEqual(snapshot.dialogs.first?.lastMessageRead, true)
    }

    func testSyncBatchDedupFixture() async throws {
        try await ctx.bootstrap()

        try await ctx.runScenario(FixtureScenario(name: "sync dedup", events: [
            .incoming(opponent: "02peer_dedup", messageId: "dup-1", timestamp: 300, text: "a"),
            .incoming(opponent: "02peer_dedup", messageId: "dup-1", timestamp: 300, text: "a"),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        XCTAssertEqual(snapshot.messages.count, 1)
        XCTAssertEqual(snapshot.messages.first?.messageId, "dup-1")
    }

    func testSavedMessagesFixture() async throws {
        try await ctx.bootstrap()

        try await ctx.runScenario(FixtureScenario(name: "saved", events: [
            .outgoing(opponent: ctx.account, messageId: "self-1", timestamp: 400, text: "note"),
            .markDelivered(opponent: ctx.account, messageId: "self-1"),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        XCTAssertEqual(snapshot.messages.count, 1)
        XCTAssertEqual(snapshot.messages.first?.dialogKey, ctx.account)
        XCTAssertEqual(snapshot.dialogs.first?.opponentKey, ctx.account)
        XCTAssertEqual(snapshot.dialogs.first?.unreadCount, 0)
    }

    func testGroupConversationDbFlowSafeFixture() async throws {
        try await ctx.bootstrap()

        try await ctx.runScenario(FixtureScenario(name: "group safe", events: [
            .incomingPacket(
                from: "02group_member_a",
                to: "#group:alpha",
                messageId: "g-1",
                timestamp: 500,
                text: "group hi"
            ),
            .incomingPacket(
                from: "02conversation_member",
                to: "conversation:room42",
                messageId: "c-1",
                timestamp: 501,
                text: "conv hi"
            ),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        let groupMessage = snapshot.messages.first { $0.messageId == "g-1" }
        let conversationMessage = snapshot.messages.first { $0.messageId == "c-1" }

        XCTAssertEqual(groupMessage?.dialogKey, "#group:alpha")
        XCTAssertEqual(conversationMessage?.dialogKey, "conversation:room42")

        let groupDialog = snapshot.dialogs.first { $0.opponentKey == "#group:alpha" }
        let conversationDialog = snapshot.dialogs.first { $0.opponentKey == "conversation:room42" }
        XCTAssertEqual(groupDialog?.iHaveSent, true)
        XCTAssertEqual(groupDialog?.isRequest, false)
        XCTAssertEqual(conversationDialog?.iHaveSent, true)
        XCTAssertEqual(conversationDialog?.isRequest, false)
    }

    func testAttachmentsOnlyLastMessageFixture() async throws {
        try await ctx.bootstrap()

        let imageAttachment = MessageAttachment(id: "att-1", preview: "", blob: "", type: .image)
        try await ctx.runScenario(FixtureScenario(name: "attachments only", events: [
            .incoming(opponent: "02peer_media", messageId: "media-1", timestamp: 600, text: "", attachments: [imageAttachment]),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        XCTAssertEqual(snapshot.messages.first?.hasAttachments, true)
        XCTAssertEqual(snapshot.dialogs.first?.lastMessage, "Photo")
    }

    func testGroupReadPacketMarksOutgoingAsReadFixture() async throws {
        try await ctx.bootstrap()

        try await ctx.runScenario(FixtureScenario(name: "group read", events: [
            .outgoing(opponent: "#group:alpha", messageId: "g-out-1", timestamp: 610, text: "hello group"),
            .markDelivered(opponent: "#group:alpha", messageId: "g-out-1"),
            .applyReadPacket(from: "02group_member_b", to: "#group:alpha"),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        let message = snapshot.messages.first { $0.messageId == "g-out-1" }
        let dialog = snapshot.dialogs.first { $0.opponentKey == "#group:alpha" }

        XCTAssertEqual(message?.fromMe, true)
        XCTAssertEqual(message?.read, true)
        XCTAssertEqual(dialog?.lastMessageRead, true)
    }

    func testCallAttachmentDecodeAndStorageFixture() async throws {
        try await ctx.bootstrap()

        let callAttachment = MessageAttachment(
            id: "call-1",
            preview: "",
            blob: "",
            type: .call
        )

        try await ctx.runScenario(FixtureScenario(name: "call attachment", events: [
            .incomingPacket(
                from: "02peer_call",
                to: ctx.account,
                messageId: "call-msg-1",
                timestamp: 620,
                text: "",
                attachments: [callAttachment]
            ),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        XCTAssertEqual(snapshot.messages.first?.hasAttachments, true)
        XCTAssertEqual(snapshot.dialogs.first?.lastMessage, "Call")
    }

    func testRequestToChatPromotionAndCursorMonotonicityFixture() async throws {
        try await ctx.bootstrap()

        try await ctx.runScenario(FixtureScenario(name: "request to chat", events: [
            .incoming(opponent: "02peer_promote", messageId: "rq-1", timestamp: 700, text: "ping"),
            .outgoing(opponent: "02peer_promote", messageId: "rq-2", timestamp: 701, text: "pong"),
            .saveSyncCursor(1_700_000_001_000),
            .saveSyncCursor(1_700_000_000_900),
            .saveSyncCursor(1_700_000_001_200),
        ]))

        let snapshot = try ctx.normalizedSnapshot()
        let dialog = snapshot.dialogs.first { $0.opponentKey == "02peer_promote" }
        XCTAssertEqual(dialog?.iHaveSent, true)
        XCTAssertEqual(dialog?.isRequest, false)
        XCTAssertEqual(snapshot.syncCursor, 1_700_000_001_200)
    }
}
