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

import Foundation
public import LibSignalClient

public enum MessageSenderError: Error, UserErrorDescriptionProvider {
    case blockedContactRecipient
    case threadMissing

    public var localizedDescription: String {
        switch self {
        case .blockedContactRecipient:
            return OWSLocalizedString(
                "ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_BLOCK_LIST",
                comment: "Error message indicating that message send failed due to block list",
            )
        case .threadMissing:
            return OWSLocalizedString(
                "MESSAGE_STATUS_SEND_FAILED",
                comment: "Label indicating that a message failed to send.",
            )
        }
    }
}

// MARK: -

extension Error {
    public var isFatalError: Bool {
        switch self {
        case is MessageSenderNoSessionForTransientMessageError:
            return true
        case is UntrustedIdentityError:
            return true
        case is MessageDeletedBeforeSentError:
            return true
        default:
            // Default to NOT fatal.
            return false
        }
    }
}

// MARK: -

public class MessageSenderNoSuchSignalRecipientError: CustomNSError, UserErrorDescriptionProvider {
    // NSError bridging: the domain of the error.
    public static let errorDomain = OWSError.errorDomain

    // NSError bridging: the error code within the given domain.
    public var errorCode: Int { OWSErrorCode.noSuchSignalRecipient.rawValue }

    // NSError bridging: the error code within the given domain.
    public var errorUserInfo: [String: Any] {
        [NSLocalizedDescriptionKey: localizedDescription]
    }

    public var localizedDescription: String {
        OWSLocalizedString(
            "ERROR_DESCRIPTION_UNREGISTERED_RECIPIENT",
            comment: "Error message when attempting to send message",
        )
    }

    public class func isNoSuchSignalRecipientError(_ error: Error?) -> Bool {
        error is MessageSenderNoSuchSignalRecipientError
    }

    public init() {}
}

// MARK: -

public class MessageSenderErrorNoValidRecipients: CustomNSError, UserErrorDescriptionProvider {
    static var asNSError: NSError {
        MessageSenderErrorNoValidRecipients() as Error as NSError
    }

    // NSError bridging: the domain of the error.
    public static let errorDomain = OWSError.errorDomain

    // NSError bridging: the error code within the given domain.
    public var errorCode: Int { OWSErrorCode.messageSendNoValidRecipients.rawValue }

    // NSError bridging: the error code within the given domain.
    public var errorUserInfo: [String: Any] {
        [NSLocalizedDescriptionKey: self.localizedDescription]
    }

    public var localizedDescription: String {
        OWSLocalizedString(
            "ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
            comment: "Error indicating that an outgoing message had no valid recipients.",
        )
    }
}

// MARK: -

class MessageSenderNoSessionForTransientMessageError: CustomNSError, UserErrorDescriptionProvider {
    // NSError bridging: the domain of the error.
    static let errorDomain = OWSError.errorDomain

    // NSError bridging: the error code within the given domain.
    var errorCode: Int { OWSErrorCode.noSessionForTransientMessage.rawValue }

    var localizedDescription: String {
        // These messages are never presented to the user, since these errors only
        // occur to transient messages. We only specify an error to avoid an assert.
        return OWSError.genericErrorDescription()
    }
}

// MARK: -

public class UntrustedIdentityError: CustomNSError, UserErrorDescriptionProvider {
    public let serviceId: ServiceId

    init(serviceId: ServiceId) {
        self.serviceId = serviceId
    }

    // NSError bridging: the domain of the error.
    public static let errorDomain = OWSError.errorDomain

    public static var errorCode: Int { OWSErrorCode.untrustedIdentity.rawValue }

    // NSError bridging: the error code within the given domain.
    public var errorUserInfo: [String: Any] {
        [NSLocalizedDescriptionKey: self.localizedDescription]
    }

    public var localizedDescription: String {
        let format = OWSLocalizedString(
            "FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
            comment: "action sheet header when re-sending message which failed because of untrusted identity keys",
        )
        return String.nonPluralLocalizedStringWithFormat(format, SSKEnvironment.shared.databaseStorageRef.read { tx in
            return SSKEnvironment.shared.contactManagerRef.displayName(for: SignalServiceAddress(serviceId), tx: tx).resolvedValue()
        })
    }

    // NSError bridging: the error code within the given domain.
    public var errorCode: Int { Self.errorCode }
}

public class InvalidKeySignatureError: CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
    public let serviceId: ServiceId
    public let isTerminalFailure: Bool

    init(serviceId: ServiceId, isTerminalFailure: Bool) {
        self.serviceId = serviceId
        self.isTerminalFailure = isTerminalFailure
    }

    // NSError bridging: the domain of the error.
    public static let errorDomain = OWSError.errorDomain

    public static var errorCode: Int { OWSErrorCode.invalidKeySignature.rawValue }

    // NSError bridging: the error code within the given domain.
    public var errorUserInfo: [String: Any] {
        [NSLocalizedDescriptionKey: self.localizedDescription]
    }

    public var localizedDescription: String {
        let format = OWSLocalizedString(
            "FAILED_SENDING_BECAUSE_INVALID_KEY_SIGNATURE",
            comment: "action sheet header when re-sending message which failed because of an invalid key signature",
        )
        return String.nonPluralLocalizedStringWithFormat(format, SSKEnvironment.shared.databaseStorageRef.read { tx in
            return SSKEnvironment.shared.contactManagerRef.displayName(for: SignalServiceAddress(serviceId), tx: tx).resolvedValue()
        })
    }

    // NSError bridging: the error code within the given domain.
    public var errorCode: Int { Self.errorCode }

    /// Key will continue to be invalidly signed, so no need to retry. It'll only
    /// cause us to hit the Pre-Key request rate limit.
    public var isRetryableProvider: Bool {
        !isTerminalFailure
    }
}

// MARK: -

public class SpamChallengeRequiredError: CustomNSError, UserErrorDescriptionProvider {
    // NSError bridging: the domain of the error.
    public static let errorDomain = OWSError.errorDomain

    // NSError bridging: the error code within the given domain.
    public var errorUserInfo: [String: Any] {
        [NSLocalizedDescriptionKey: self.localizedDescription]
    }

    public var localizedDescription: String {
        OWSLocalizedString(
            "ERROR_DESCRIPTION_SUSPECTED_SPAM",
            comment: "Description for errors returned from the server due to suspected spam.",
        )
    }

    // NSError bridging: the error code within the given domain.
    public var errorCode: Int { OWSErrorCode.serverRejectedSuspectedSpam.rawValue }
}

// MARK: -

class SpamChallengeResolvedError: CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
    // NSError bridging: the domain of the error.
    static let errorDomain = OWSError.errorDomain

    // NSError bridging: the error code within the given domain.
    var errorUserInfo: [String: Any] {
        [NSLocalizedDescriptionKey: self.localizedDescription]
    }

    var localizedDescription: String {
        OWSLocalizedString(
            "ERROR_DESCRIPTION_SUSPECTED_SPAM",
            comment: "Description for errors returned from the server due to suspected spam.",
        )
    }

    // NSError bridging: the error code within the given domain.
    var errorCode: Int { OWSErrorCode.serverRejectedSuspectedSpam.rawValue }

    var isRetryableProvider: Bool { true }
}

// MARK: -

class OWSRetryableMessageSenderError: Error, IsRetryableProvider {
    static var asNSError: NSError {
        OWSRetryableMessageSenderError() as Error as NSError
    }

    // MARK: - IsRetryableProvider

    var isRetryableProvider: Bool { true }
}

// MARK: -

class MessageDeletedBeforeSentError: CustomNSError {
    static var asNSError: NSError {
        MessageDeletedBeforeSentError() as Error as NSError
    }

    // NSError bridging: the domain of the error.
    static let errorDomain = OWSError.errorDomain

    // NSError bridging: the error code within the given domain.
    var errorCode: Int { OWSErrorCode.messageDeletedBeforeSent.rawValue }
}
