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

import Foundation

public struct MessageStickerDataSource {
    public let info: StickerInfo
    public let emoji: String?
    public let source: AttachmentDataSource
}

public struct ValidatedMessageStickerProto {
    public let sticker: MessageSticker
    public let proto: SSKProtoAttachmentPointer
}

public struct ValidatedMessageStickerDataSource {
    public let sticker: MessageSticker
    public let attachmentDataSource: AttachmentDataSource
}

// MARK: -

public protocol MessageStickerManager {

    func buildValidatedMessageSticker(
        from proto: SSKProtoDataMessageSticker,
    ) throws -> ValidatedMessageStickerProto

    func buildDataSource(
        fromDraft: MessageStickerDraft,
    ) async throws -> MessageStickerDataSource

    func validateMessageSticker(
        dataSource: MessageStickerDataSource,
    ) throws -> ValidatedMessageStickerDataSource

    func buildProtoForSending(
        _ messageSticker: MessageSticker,
        parentMessage: TSMessage,
        tx: DBReadTransaction,
    ) throws -> SSKProtoDataMessageSticker
}

// MARK: -

class MessageStickerManagerImpl: MessageStickerManager {

    private let attachmentStore: AttachmentStore
    private let attachmentValidator: AttachmentContentValidator

    init(
        attachmentStore: AttachmentStore,
        attachmentValidator: AttachmentContentValidator,
    ) {
        self.attachmentStore = attachmentStore
        self.attachmentValidator = attachmentValidator
    }

    func buildValidatedMessageSticker(
        from stickerProto: SSKProtoDataMessageSticker,
    ) throws -> ValidatedMessageStickerProto {
        let packID: Data = stickerProto.packID
        let packKey: Data = stickerProto.packKey
        let stickerID: UInt32 = stickerProto.stickerID
        let emoji: String? = stickerProto.emoji
        let attachmentProto: SSKProtoAttachmentPointer = stickerProto.data
        let stickerInfo = StickerInfo(packId: packID, packKey: packKey, stickerId: stickerID)

        let messageSticker = MessageSticker(info: stickerInfo, emoji: emoji)
        guard messageSticker.isValid else {
            throw StickerError.invalidInput
        }

        if attachmentProto.contentType == nil || attachmentProto.contentType == MimeType.applicationOctetStream.rawValue {
            let builder = attachmentProto.asBuilder()
            builder.setContentType(MimeType.imageWebp.rawValue)

            return ValidatedMessageStickerProto(
                sticker: messageSticker,
                proto: builder.buildInfallibly(),
            )
        } else {
            return ValidatedMessageStickerProto(
                sticker: messageSticker,
                proto: attachmentProto,
            )
        }
    }

    func buildDataSource(
        fromDraft draft: MessageStickerDraft,
    ) async throws -> MessageStickerDataSource {
        let validatedDataSource = try await attachmentValidator.validateDataContents(
            draft.stickerData,
            mimeType: draft.stickerType.mimeType,
            renderingFlag: .default,
            sourceFilename: nil,
        )
        return MessageStickerDataSource(
            info: draft.info,
            emoji: draft.emoji,
            source: .pendingAttachment(validatedDataSource),
        )
    }

    func validateMessageSticker(
        dataSource: MessageStickerDataSource,
    ) throws -> ValidatedMessageStickerDataSource {
        let messageSticker = MessageSticker(info: dataSource.info, emoji: dataSource.emoji)
        guard messageSticker.isValid else {
            throw StickerError.invalidInput
        }

        return ValidatedMessageStickerDataSource(
            sticker: messageSticker,
            attachmentDataSource: dataSource.source,
        )
    }

    func buildProtoForSending(
        _ messageSticker: MessageSticker,
        parentMessage: TSMessage,
        tx: DBReadTransaction,
    ) throws -> SSKProtoDataMessageSticker {

        guard
            let parentMessageRowId = parentMessage.sqliteRowId,
            let referencedAttachment = attachmentStore.fetchAnyReferencedAttachment(
                for: .messageSticker(messageRowId: parentMessageRowId),
                tx: tx,
            )
        else {
            throw OWSAssertionError("Could not find sticker attachment")
        }

        guard let attachmentProto = referencedAttachment.asProtoForSending() else {
            throw OWSAssertionError("Failed to make proto for sending from attachment!")
        }

        let protoBuilder = SSKProtoDataMessageSticker.builder(
            packID: messageSticker.packId,
            packKey: messageSticker.packKey,
            stickerID: messageSticker.stickerId,
            data: attachmentProto,
        )

        if let emoji = messageSticker.emoji?.nilIfEmpty {
            protoBuilder.setEmoji(emoji)
        }

        return try protoBuilder.build()
    }
}
