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

import SignalServiceKit
import SignalUI

protocol MessageRequestDelegate: AnyObject {
    func messageRequestViewDidTapBlock(mode: MessageRequestMode)
    func messageRequestViewDidTapDelete()
    func messageRequestViewDidTapAccept(mode: MessageRequestMode, unblockThread: Bool, unhideRecipient: Bool)
    func messageRequestViewDidTapUnblock(mode: MessageRequestMode)
    func messageRequestViewDidTapReport()
    func messageRequestViewDidTapLearnMore()
}

// MARK: -

public enum MessageRequestMode: UInt {
    case none
    case contactOrGroupRequest
    case groupInviteRequest
}

// MARK: -

public struct MessageRequestType: Equatable {
    let isGroupV1Thread: Bool
    let isGroupV2Thread: Bool
    let isThreadBlocked: Bool
    let hasSentMessages: Bool
    let isThreadFromHiddenRecipient: Bool
    let hasReportedSpam: Bool
}

// MARK: -

class MessageRequestView: UIStackView {

    enum LocalizedStrings {
        static let block = OWSLocalizedString(
            "MESSAGE_REQUEST_VIEW_BLOCK_BUTTON",
            comment: "A button used to block a user on an incoming message request."
        )
        static let unblock = OWSLocalizedString(
            "MESSAGE_REQUEST_VIEW_UNBLOCK_BUTTON",
            comment: "A button used to unlock a blocked conversation."
        )
        static let delete = OWSLocalizedString(
            "MESSAGE_REQUEST_VIEW_DELETE_BUTTON",
            comment: "incoming message request button text which deletes a conversation"
        )
        static let report = OWSLocalizedString(
            "MESSAGE_REQUEST_VIEW_REPORT_BUTTON",
            comment: "incoming message request button text which reports a conversation as spam"
        )
        static let accept = OWSLocalizedString(
            "MESSAGE_REQUEST_VIEW_ACCEPT_BUTTON",
            comment: "A button used to accept a user on an incoming message request."
        )
        static let `continue` = OWSLocalizedString(
            "MESSAGE_REQUEST_VIEW_CONTINUE_BUTTON",
            comment: "A button used to continue a conversation and share your profile."
        )
    }

    private let thread: TSThread
    private let mode: MessageRequestMode
    private let messageRequestType: MessageRequestType

    private var isGroupV1Thread: Bool {
        messageRequestType.isGroupV1Thread
    }
    private var isGroupV2Thread: Bool {
        messageRequestType.isGroupV2Thread
    }
    private var isThreadBlocked: Bool {
        messageRequestType.isThreadBlocked
    }
    private var hasSentMessages: Bool {
        messageRequestType.hasSentMessages
    }
    private var isThreadFromHiddenRecipient: Bool {
        messageRequestType.isThreadFromHiddenRecipient
    }
    private var hasReportedSpam: Bool {
        messageRequestType.hasReportedSpam
    }

    weak var delegate: MessageRequestDelegate?

    init(threadViewModel: ThreadViewModel) {
        let thread = threadViewModel.threadRecord
        self.thread = thread
        self.messageRequestType = Self.databaseStorage.read { transaction in
            Self.messageRequestType(forThread: thread, transaction: transaction)
        }

        if let groupThread = thread as? TSGroupThread,
            groupThread.isGroupV2Thread {
            self.mode = (groupThread.isLocalUserInvitedMember
                ? .groupInviteRequest
                : .contactOrGroupRequest)
        } else {
            self.mode = .contactOrGroupRequest
        }

        super.init(frame: .zero)

        // We want the background to extend to the bottom of the screen
        // behind the safe area, so we add that inset to our bottom inset
        // instead of pinning this view to the safe area
        let safeAreaInset = safeAreaInsets.bottom

        autoresizingMask = .flexibleHeight

        axis = .vertical
        spacing = 11
        layoutMargins = UIEdgeInsets(top: 16, leading: 16, bottom: 20 + safeAreaInset, trailing: 16)
        isLayoutMarginsRelativeArrangement = true

        let backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .prominent))
        addSubview(backgroundView)
        backgroundView.autoPinEdgesToSuperviewEdges()

        switch mode {
        case .none:
            owsFailDebug("Invalid mode.")
        case .contactOrGroupRequest:
            addArrangedSubview(prepareMessageRequestPrompt())
            addArrangedSubview(prepareMessageRequestButtons())
        case .groupInviteRequest:
            addArrangedSubview(prepareGroupV2InvitePrompt())
            addArrangedSubview(prepareGroupV2InviteButtons())
        }
    }

    public static func messageRequestType(forThread thread: TSThread,
                                          transaction: SDSAnyReadTransaction) -> MessageRequestType {
        let isGroupV1Thread = thread.isGroupV1Thread
        let isGroupV2Thread = thread.isGroupV2Thread
        let isThreadBlocked = blockingManager.isThreadBlocked(thread, transaction: transaction)
        var isThreadFromHiddenRecipient = false
        if let thread = thread as? TSContactThread {
            isThreadFromHiddenRecipient = DependenciesBridge.shared.recipientHidingManager.isHiddenAddress(
                thread.contactAddress,
                tx: transaction.asV2Read
            )
        }
        let finder = InteractionFinder(threadUniqueId: thread.uniqueId)
        let hasSentMessages = finder.existsOutgoingMessage(transaction: transaction)
        let hasReportedSpam = finder.hasUserReportedSpam(transaction: transaction)
        return MessageRequestType(
            isGroupV1Thread: isGroupV1Thread,
            isGroupV2Thread: isGroupV2Thread,
            isThreadBlocked: isThreadBlocked,
            hasSentMessages: hasSentMessages,
            isThreadFromHiddenRecipient: isThreadFromHiddenRecipient,
            hasReportedSpam: hasReportedSpam
        )
    }

    required init(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var intrinsicContentSize: CGSize {
        return .zero
    }

    // MARK: - Message Request

    // This is used for:
    //
    // * Contact threads
    // * v1 groups
    // * v2 groups if user does not have a pending invite.
    func prepareMessageRequestPrompt() -> UITextView {
        if thread.isGroupThread {
            let string: String
            var appendLearnMoreLink = false
            if thread.isGroupV1Thread {
                if isThreadBlocked {
                    string = OWSLocalizedString(
                        "MESSAGE_REQUEST_VIEW_BLOCKED_GROUP_PROMPT",
                        comment: "A prompt notifying that the user must unblock this group to continue."
                    )
                } else if hasSentMessages {
                    string = OWSLocalizedString(
                        "MESSAGE_REQUEST_VIEW_EXISTING_GROUP_PROMPT",
                        comment: "A prompt notifying that the user must share their profile with this group."
                    )
                    appendLearnMoreLink = true
                } else {
                    string = OWSLocalizedString(
                        "MESSAGE_REQUEST_VIEW_NEW_GROUP_PROMPT",
                        comment: "A prompt asking if the user wants to accept a group invite."
                    )
                }
            } else {
                owsAssertDebug(thread.isGroupV2Thread)

                if isThreadBlocked {
                    string = OWSLocalizedString(
                        "MESSAGE_REQUEST_VIEW_BLOCKED_GROUP_PROMPT_V2",
                        comment: "A prompt notifying that the user must unblock this group to continue."
                    )
                } else {
                    string = OWSLocalizedString(
                        "MESSAGE_REQUEST_VIEW_NEW_GROUP_PROMPT_V2",
                        comment: "A prompt asking if the user wants to accept a group invite."
                    )
                }
            }

            return prepareTextView(
                attributedString: NSAttributedString(string: string, attributes: [
                    .font: UIFont.dynamicTypeSubheadlineClamped,
                    .foregroundColor: Theme.secondaryTextAndIconColor
                ]),
                appendLearnMoreLink: appendLearnMoreLink
            )
        } else if let thread = thread as? TSContactThread {
            let formatString: String
            var appendLearnMoreLink = false

            if isThreadBlocked {
                formatString = OWSLocalizedString(
                    "MESSAGE_REQUEST_VIEW_BLOCKED_CONTACT_PROMPT_FORMAT",
                    comment: "A prompt notifying that the user must unblock this conversation to continue. Embeds {{contact name}}."
                )
            } else if isThreadFromHiddenRecipient {
                formatString = OWSLocalizedString("MESSAGE_REQUEST_VIEW_REMOVED_CONTACT_PROMPT_FORMAT", comment: "A prompt asking if the user wants to accept a conversation invite from a person whom they previously removed. Embeds {{contact name}}.")

            } else if hasSentMessages {
                formatString = OWSLocalizedString(
                    "MESSAGE_REQUEST_VIEW_EXISTING_CONTACT_PROMPT_FORMAT",
                    comment: "A prompt notifying that the user must share their profile with this conversation. Embeds {{contact name}}."
                )
                appendLearnMoreLink = true
            } else {
                formatString = OWSLocalizedString(
                    "MESSAGE_REQUEST_VIEW_NEW_CONTACT_PROMPT_FORMAT",
                    comment: "A prompt asking if the user wants to accept a conversation invite. Embeds {{contact name}}."
                )
            }

            let shortName = databaseStorage.read { transaction in
                return self.contactsManager.displayName(for: thread.contactAddress, tx: transaction).resolvedValue(useShortNameIfAvailable: true)
            }

            return preparePromptTextView(
                formatString: formatString,
                embeddedString: shortName,
                appendLearnMoreLink: appendLearnMoreLink
            )
        } else {
            owsFailDebug("unexpected thread type")
            return UITextView()
        }
    }

    // This is used for:
    //
    // * Contact threads
    // * v1 groups
    // * v2 groups if user does not have a pending invite.
    func prepareMessageRequestButtons() -> UIStackView {
        let mode = self.mode
        var buttons = [UIView]()

        if isThreadBlocked {
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.delete,
                    titleColor: .ows_accentRed
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapDelete()
                }
            )
            if !hasReportedSpam {
                buttons.append(
                    prepareButton(
                        title: LocalizedStrings.report,
                        titleColor: .ows_accentRed
                    ) { [weak self] in
                        self?.delegate?.messageRequestViewDidTapReport()
                    }
                )
            }
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.unblock,
                    titleColor: Theme.primaryTextColor
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapUnblock(mode: mode)
                }
            )
        } else if isThreadFromHiddenRecipient {
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.block,
                    titleColor: .ows_accentRed
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapBlock(mode: mode)
                }
            )
            if !hasReportedSpam {
                buttons.append(
                    prepareButton(
                        title: LocalizedStrings.report,
                        titleColor: .ows_accentRed
                    ) { [weak self] in
                        self?.delegate?.messageRequestViewDidTapReport()
                    }
                )
            } else {
                buttons.append(
                    prepareButton(
                        title: LocalizedStrings.delete,
                        titleColor: .ows_accentRed
                    ) { [weak self] in
                        self?.delegate?.messageRequestViewDidTapDelete()
                    }
                )
            }
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.accept,
                    titleColor: Theme.primaryTextColor
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapAccept(mode: mode, unblockThread: false, unhideRecipient: true)
                }
            )
        } else if hasSentMessages {
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.block,
                    titleColor: .ows_accentRed
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapBlock(mode: mode)
                }
            )
            if !hasReportedSpam {
                buttons.append(
                    prepareButton(
                        title: LocalizedStrings.report,
                        titleColor: .ows_accentRed
                    ) { [weak self] in
                        self?.delegate?.messageRequestViewDidTapReport()
                    }
                )
            } else {
                buttons.append(
                    prepareButton(
                        title: LocalizedStrings.delete,
                        titleColor: .ows_accentRed
                    ) { [weak self] in
                        self?.delegate?.messageRequestViewDidTapDelete()
                    }
                )
            }
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.continue,
                    titleColor: Theme.primaryTextColor
                ) { [weak self] in
                    // This is the same action as accepting the message request, but displays
                    // with slightly different visuals if the user has already been messaging
                    // this user in the past but didn't share their profile.
                    self?.delegate?.messageRequestViewDidTapAccept(mode: mode, unblockThread: false, unhideRecipient: false)
                }
            )
        } else {
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.block,
                    titleColor: .ows_accentRed
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapBlock(mode: mode)
                }
            )
            if !hasReportedSpam {
                buttons.append(
                    prepareButton(
                        title: LocalizedStrings.report,
                        titleColor: .ows_accentRed
                    ) { [weak self] in
                        self?.delegate?.messageRequestViewDidTapReport()
                    }
                )
            } else {
                buttons.append(
                    prepareButton(
                        title: LocalizedStrings.delete,
                        titleColor: .ows_accentRed
                    ) { [weak self] in
                        self?.delegate?.messageRequestViewDidTapDelete()
                    }
                )
            }
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.accept,
                    titleColor: Theme.primaryTextColor
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapAccept(mode: mode, unblockThread: false, unhideRecipient: false)
                }
            )
        }

        return prepareButtonStack(buttons)
    }

    // MARK: - Group V2 Invites

    func prepareGroupV2InvitePrompt() -> UITextView {
        let string = OWSLocalizedString(
            "MESSAGE_REQUEST_VIEW_NEW_GROUP_PROMPT",
            comment: "A prompt asking if the user wants to accept a group invite."
        )

        return prepareTextView(
            attributedString: NSAttributedString(string: string, attributes: [
                .font: UIFont.dynamicTypeSubheadlineClamped,
                .foregroundColor: Theme.secondaryTextAndIconColor
            ]),
            appendLearnMoreLink: false
        )
    }

    func prepareGroupV2InviteButtons() -> UIStackView {
        let mode = self.mode
        var buttons = [UIView]()

        buttons.append(
            prepareButton(
                title: LocalizedStrings.block,
                titleColor: .ows_accentRed
            ) { [weak self] in
                self?.delegate?.messageRequestViewDidTapBlock(mode: mode)
            }
        )

        if !hasReportedSpam {
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.report,
                    titleColor: .ows_accentRed
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapReport()
                }
            )
        } else {
            buttons.append(
                prepareButton(
                    title: LocalizedStrings.delete,
                    titleColor: .ows_accentRed
                ) { [weak self] in
                    self?.delegate?.messageRequestViewDidTapDelete()
                }
            )
        }

        buttons.append(
            prepareButton(
                title: LocalizedStrings.accept,
                titleColor: Theme.primaryTextColor
            ) { [weak self] in
                self?.delegate?.messageRequestViewDidTapAccept(mode: mode, unblockThread: false, unhideRecipient: false)
            }
        )
        return prepareButtonStack(buttons)
    }

    // MARK: -

    private func prepareButton(title: String, titleColor: UIColor, touchHandler: @escaping () -> Void) -> OWSRoundedButton {
        let button = OWSRoundedButton(title: title, block: touchHandler)
        button.setTitleColor(titleColor, for: .normal)
        button.titleLabel?.font = .dynamicTypeBodyClamped.semibold()
        button.dimsWhenHighlighted = true
        button.backgroundColor = Theme.isDarkThemeEnabled ? UIColor(white: 1, alpha: 0.16) : UIColor(white: 0, alpha: 0.08)
        return button
    }

    private func preparePromptTextView(formatString: String, embeddedString: String, appendLearnMoreLink: Bool) -> UITextView {
        let centered = NSMutableParagraphStyle()
        centered.alignment = .center
        let defaultAttributes: AttributedFormatArg.Attributes = [
            .font: UIFont.dynamicTypeSubheadlineClamped,
            .foregroundColor: Theme.secondaryTextAndIconColor,
            .paragraphStyle: centered,
        ]

        let attributesForEmbedded: AttributedFormatArg.Attributes = [
            .font: UIFont.dynamicTypeSubheadlineClamped.semibold(),
            .foregroundColor: Theme.secondaryTextAndIconColor
        ]

        let attributedString = NSAttributedString.make(
            fromFormat: formatString,
            attributedFormatArgs: [.string(embeddedString, attributes: attributesForEmbedded)],
            defaultAttributes: defaultAttributes
        )

        return prepareTextView(attributedString: attributedString, appendLearnMoreLink: appendLearnMoreLink)
    }

    private func prepareTextView(attributedString: NSAttributedString, appendLearnMoreLink: Bool) -> UITextView {
        let textView = UITextView()
        textView.isOpaque = false
        textView.isEditable = false
        textView.contentInset = .zero
        textView.textContainer.lineFragmentPadding = 0
        textView.isScrollEnabled = false
        textView.backgroundColor = .clear
        textView.linkTextAttributes = [
            .foregroundColor: Theme.accentBlueColor
        ]

        if appendLearnMoreLink {
            textView.attributedText = .composed(of: [
                attributedString,
                " ",
                CommonStrings.learnMore.styled(
                    with: .link(URL(string: "https://support.signal.org/hc/articles/360007459591")!),
                    .font(.dynamicTypeSubheadlineClamped)
                )
            ])
        } else {
            textView.attributedText = attributedString
        }

        return textView
    }

    private func prepareButtonStack(_ buttons: [UIView]) -> UIStackView {
        let buttonsStack = UIStackView(arrangedSubviews: buttons)
        buttonsStack.spacing = 11.5
        buttonsStack.distribution = .fillEqually
        return buttonsStack
    }
}
