import Foundation

/**
 A result from sending a key to the device.
 */
enum MessageResult: UInt8 {
    
    // MARK: Device status

    /// The message was accepted.
    case messageAccepted = 0
    
    /// The web socket received text while waiting for binary data.
    case textReceived = 1

    /// An unexpected socket event occured while performing the exchange.
    case unexpectedSocketEvent = 2

    /// The received message size is invalid.
    case invalidMessageSizeFromRemote = 3

    /// The message signature was incorrect.
    case invalidSignatureFromRemote = 4

    /// The server challenge of the message did not match previous messages
    case invalidServerChallengeFromRemote = 5

    /// The client challenge of the message did not match previous messages
    case invalidClientChallengeFromRemote = 6

    /// An unexpected or unsupported message type was received
    case invalidMessageTypeFromRemote = 7

    /// A message is already being processed
    case tooManyRequests = 8
    
    /// The received message result was not ``messageAccepted``
    case invalidMessageResultFromRemote = 9

    /// An invalid Url parameter was set sending a message to the device over a local connection
    case invalidUrlParameter = 10
    
    // MARK: Server status
    
    /// The body data posting a message was missing or of wrong length
    case noOrInvalidBodyDataFromRemote = 21
    
    /// The authentication token for the server was invalid
    case invalidServerAuthenticationFromRemote = 22
    
    /// The request took too long to complete
    case deviceTimedOut = 23
    
    /// The device is not connected to the server via web socket
    case deviceNotConnected = 24
    
    /// The device sent a response of invalid size
    case invalidMessageSizeFromDevice = 25
    
    /// The header with the authentication token was missing or invalid (not a hex string) from a server request.
    case missingOrInvalidAuthenticationHeaderFromRemote = 26
    
    /// The server produced an internal error (500)
    case internalServerError = 27
    
    // MARK: Remote status

    /// The initial state without information about the connection
    case notChecked = 30
    
    /// The url string is not a valid url
    case serverUrlInvalid = 31
    
    /// The device key or auth token is missing for a request.
    case noKeyAvailable = 32
    
    /// The Sesame server behind the proxy could not be found (502)
    case serviceBehindProxyUnavailable = 33
    
    /// The server url could not be found (404)
    case pathOnServerNotFound = 34
    
    /// The url session request returned an unknown response
    case unexpectedUrlResponseType = 35
    
    /// The request to the server returned an unhandled HTTP code
    case unexpectedServerResponseCode = 36
    
    /// A valid server challenge was received
    case deviceAvailable = 37
    
    case invalidSignatureFromDevice = 38
    
    case invalidMessageTypeFromDevice = 39
    
    case unknownMessageResultFromDevice = 40
    
    /// The device sent a message with an invalid client challenge
    case invalidClientChallengeFromDevice = 41
    
    /// The device used an invalid server challenge in a response
    case invalidServerChallengeFromDevice = 42
    
    /// The unlock process was successfully completed
    case unlocked = 43
}

extension MessageResult: Error {
    
}

extension MessageResult: CustomStringConvertible {

    var description: String {
        switch self {
        case .messageAccepted:
            return "Message accepted"
        case .textReceived:
            return "The device received unexpected text"
        case .unexpectedSocketEvent:
            return "Unexpected socket event for the device"
        case .invalidMessageSizeFromRemote:
            return "Invalid message data from remote"
        case .invalidSignatureFromRemote:
            return "Message authentication failed"
        case .invalidServerChallengeFromRemote:
            return "Remote used wrong server challenge"
        case .invalidClientChallengeFromRemote:
            return "Wrong client challenge sent"
        case .invalidMessageTypeFromRemote:
            return "Message type from remote invalid"
        case .tooManyRequests:
            return "Device busy"
        case .invalidMessageResultFromRemote:
            return "Invalid message result"
        case .invalidUrlParameter:
            return "The url parameter could not be found"
        
        case .noOrInvalidBodyDataFromRemote:
            return "Invalid body data in server request"
        case .invalidServerAuthenticationFromRemote:
            return "Invalid server token"
        case .deviceTimedOut:
            return "The device did not respond"
        case .deviceNotConnected:
            return "Device not connected to server"
        case .invalidMessageSizeFromDevice:
            return "Invalid device message size"
        case .missingOrInvalidAuthenticationHeaderFromRemote:
            return "Invalid server token format"
        case .internalServerError:
            return "Internal server error"
        
        case .notChecked:
            return "Not checked"
        case .serverUrlInvalid:
            return "Invalid server url"
        case .noKeyAvailable:
            return "No key available"
        case .serviceBehindProxyUnavailable:
            return "Service behind proxy not found"
        case .pathOnServerNotFound:
            return "Invalid server path"
        case .unexpectedUrlResponseType:
            return "Unexpected URL response"
        case .unexpectedServerResponseCode:
            return "Unexpected server response code"
        case .deviceAvailable:
            return "Device available"
        case .invalidSignatureFromDevice:
            return "Invalid device signature"
        case .invalidMessageTypeFromDevice:
            return "Message type from device invalid"
        case .unknownMessageResultFromDevice:
            return "Unknown message result"
        case .invalidClientChallengeFromDevice:
            return "Device used wrong client challenge"
        case .invalidServerChallengeFromDevice:
            return "Invalid"
        case .unlocked:
            return "Unlocked"
        }
    }
}

extension MessageResult: Codable {
    
}

extension MessageResult {

    var encoded: Data {
        Data([rawValue])
    }
}

extension MessageResult {
    
    init(httpCode: Int) {
        switch httpCode {
        case 200: self = .messageAccepted
        case 204: self = .noOrInvalidBodyDataFromRemote
        case 403: self = .invalidServerAuthenticationFromRemote
        case 404: self = .pathOnServerNotFound
        case 408: self = .deviceTimedOut
        case 412: self = .deviceNotConnected
        case 413: self = .invalidMessageSizeFromDevice
        case 422: self = .missingOrInvalidAuthenticationHeaderFromRemote
        case 429: self = .tooManyRequests
        case 500: self = .internalServerError
        case 501: self = .unexpectedServerResponseCode
        case 502: self = .serviceBehindProxyUnavailable
        default: self = .unexpectedServerResponseCode
        }
    }
    
    var statusCode: Int {
        switch self {
        case .messageAccepted: return 200 // ok
        case .noOrInvalidBodyDataFromRemote: return 204 // noContent
        case .invalidServerAuthenticationFromRemote: return 403 // forbidden
        case .pathOnServerNotFound: return 404 // notFound
        case .deviceTimedOut: return 408 // requestTimeout
        case .invalidMessageSizeFromRemote: return 411 // lengthRequired
        case .deviceNotConnected: return 412 // preconditionFailed
        case .invalidMessageSizeFromDevice: return 413 // payloadTooLarge
        case .missingOrInvalidAuthenticationHeaderFromRemote: return 422 // unprocessableEntity
        case .tooManyRequests: return 429 // tooManyRequests
        case .internalServerError: return 500 // internalServerError
        case .unexpectedServerResponseCode: return 501 // notImplemented
        case .serviceBehindProxyUnavailable: return 502 // badGateway
        default: return 501 // == unexpectedServerResponseCode
        }
    }
}
