use crate::sip::Error;

#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Default)]
pub enum StatusCode {
    Trying,
    Ringing,
    CallIsBeingForwarded,
    Queued,
    SessionProgress,
    EarlyDialogTerminated,
    #[default]
    OK,
    Accepted,
    NoNotification,
    MultipleChoices,
    MovedPermanently,
    MovedTemporarily,
    UseProxy,
    AlternativeService,
    BadRequest,
    Unauthorized,
    PaymentRequired,
    Forbidden,
    NotFound,
    MethodNotAllowed,
    NotAcceptable,
    ProxyAuthenticationRequired,
    RequestTimeout,
    Conflict,
    Gone,
    LengthRequired,
    ConditionalRequestFailed,
    RequestEntityTooLarge,
    RequestUriTooLong,
    UnsupportedMediaType,
    UnsupportedUriScheme,
    UnknownResourcePriority,
    BadExtension,
    ExtensionRequired,
    SessionIntervalTooSmall,
    IntervalTooBrief,
    BadLocationInformation,
    UseIdentityHeader,
    ProvideReferrerIdentity,
    AnonymityDisallowed,
    BadIdentityInfo,
    UnsupportedCertificate,
    InvalidIdentityHeader,
    FirstHopLacksOutboundSupport,
    MaxBreadthExceeded,
    BadInfoPackage,
    ConsentNeeded,
    TemporarilyUnavailable,
    CallTransactionDoesNotExist,
    LoopDetected,
    TooManyHops,
    AddressIncomplete,
    Ambiguous,
    BusyHere,
    RequestTerminated,
    NotAcceptableHere,
    BadEvent,
    RequestPending,
    Undecipherable,
    SecurityAgreementRequired,
    ServerInternalError,
    NotImplemented,
    BadGateway,
    ServiceUnavailable,
    ServerTimeOut,
    VersionNotSupported,
    MessageTooLarge,
    PreconditionFailure,
    BusyEverywhere,
    Decline,
    DoesNotExistAnywhere,
    NotAcceptableGlobal,
    Unwanted,
    Other(u16, String),
}

impl StatusCode {
    pub fn code(&self) -> u16 {
        match self {
            Self::Trying => 100,
            Self::Ringing => 180,
            Self::CallIsBeingForwarded => 181,
            Self::Queued => 182,
            Self::SessionProgress => 183,
            Self::EarlyDialogTerminated => 199,
            Self::OK => 200,
            Self::Accepted => 202,
            Self::NoNotification => 204,
            Self::MultipleChoices => 300,
            Self::MovedPermanently => 301,
            Self::MovedTemporarily => 302,
            Self::UseProxy => 305,
            Self::AlternativeService => 380,
            Self::BadRequest => 400,
            Self::Unauthorized => 401,
            Self::PaymentRequired => 402,
            Self::Forbidden => 403,
            Self::NotFound => 404,
            Self::MethodNotAllowed => 405,
            Self::NotAcceptable => 406,
            Self::ProxyAuthenticationRequired => 407,
            Self::RequestTimeout => 408,
            Self::Conflict => 409,
            Self::Gone => 410,
            Self::LengthRequired => 411,
            Self::ConditionalRequestFailed => 412,
            Self::RequestEntityTooLarge => 413,
            Self::RequestUriTooLong => 414,
            Self::UnsupportedMediaType => 415,
            Self::UnsupportedUriScheme => 416,
            Self::UnknownResourcePriority => 417,
            Self::BadExtension => 420,
            Self::ExtensionRequired => 421,
            Self::SessionIntervalTooSmall => 422,
            Self::IntervalTooBrief => 423,
            Self::BadLocationInformation => 424,
            Self::UseIdentityHeader => 428,
            Self::ProvideReferrerIdentity => 429,
            Self::AnonymityDisallowed => 433,
            Self::BadIdentityInfo => 436,
            Self::UnsupportedCertificate => 437,
            Self::InvalidIdentityHeader => 438,
            Self::FirstHopLacksOutboundSupport => 439,
            Self::MaxBreadthExceeded => 440,
            Self::BadInfoPackage => 469,
            Self::ConsentNeeded => 470,
            Self::TemporarilyUnavailable => 480,
            Self::CallTransactionDoesNotExist => 481,
            Self::LoopDetected => 482,
            Self::TooManyHops => 483,
            Self::AddressIncomplete => 484,
            Self::Ambiguous => 485,
            Self::BusyHere => 486,
            Self::RequestTerminated => 487,
            Self::NotAcceptableHere => 488,
            Self::BadEvent => 489,
            Self::RequestPending => 491,
            Self::Undecipherable => 493,
            Self::SecurityAgreementRequired => 494,
            Self::ServerInternalError => 500,
            Self::NotImplemented => 501,
            Self::BadGateway => 502,
            Self::ServiceUnavailable => 503,
            Self::ServerTimeOut => 504,
            Self::VersionNotSupported => 505,
            Self::MessageTooLarge => 513,
            Self::PreconditionFailure => 580,
            Self::BusyEverywhere => 600,
            Self::Decline => 603,
            Self::DoesNotExistAnywhere => 604,
            Self::NotAcceptableGlobal => 606,
            Self::Unwanted => 607,
            Self::Other(code, _) => *code,
        }
    }

    pub fn kind(&self) -> StatusCodeKind {
        let code = self.code();
        match code {
            100..=199 => StatusCodeKind::Provisional,
            200..=299 => StatusCodeKind::Successful,
            300..=399 => StatusCodeKind::Redirection,
            400..=499 => StatusCodeKind::RequestFailure,
            500..=599 => StatusCodeKind::ServerFailure,
            600..=699 => StatusCodeKind::GlobalFailure,
            _ => StatusCodeKind::Other,
        }
    }

    pub fn text(&self) -> &str {
        match self {
            Self::Trying => "Trying",
            Self::Ringing => "Ringing",
            Self::CallIsBeingForwarded => "Call Is Being Forwarded",
            Self::Queued => "Queued",
            Self::SessionProgress => "Session Progress",
            Self::EarlyDialogTerminated => "Early Dialog Terminated",
            Self::OK => "OK",
            Self::Accepted => "Accepted",
            Self::NoNotification => "No Notification",
            Self::MultipleChoices => "Multiple Choices",
            Self::MovedPermanently => "Moved Permanently",
            Self::MovedTemporarily => "Moved Temporarily",
            Self::UseProxy => "Use Proxy",
            Self::AlternativeService => "Alternative Service",
            Self::BadRequest => "Bad Request",
            Self::Unauthorized => "Unauthorized",
            Self::PaymentRequired => "Payment Required",
            Self::Forbidden => "Forbidden",
            Self::NotFound => "Not Found",
            Self::MethodNotAllowed => "Method Not Allowed",
            Self::NotAcceptable => "Not Acceptable",
            Self::ProxyAuthenticationRequired => "Proxy Authentication Required",
            Self::RequestTimeout => "Request Timeout",
            Self::Conflict => "Conflict",
            Self::Gone => "Gone",
            Self::LengthRequired => "Length Required",
            Self::ConditionalRequestFailed => "Conditional Request Failed",
            Self::RequestEntityTooLarge => "Request Entity Too Large",
            Self::RequestUriTooLong => "Request-URI Too Long",
            Self::UnsupportedMediaType => "Unsupported Media Type",
            Self::UnsupportedUriScheme => "Unsupported URI Scheme",
            Self::UnknownResourcePriority => "Unknown Resource-Priority",
            Self::BadExtension => "Bad Extension",
            Self::ExtensionRequired => "Extension Required",
            Self::SessionIntervalTooSmall => "Session Interval Too Small",
            Self::IntervalTooBrief => "Interval Too Brief",
            Self::BadLocationInformation => "Bad Location Information",
            Self::UseIdentityHeader => "Use Identity Header",
            Self::ProvideReferrerIdentity => "Provide Referrer Identity",
            Self::AnonymityDisallowed => "Anonymity Disallowed",
            Self::BadIdentityInfo => "Bad Identity-Info",
            Self::UnsupportedCertificate => "Unsupported Certificate",
            Self::InvalidIdentityHeader => "Invalid Identity Header",
            Self::FirstHopLacksOutboundSupport => "First Hop Lacks Outbound Support",
            Self::MaxBreadthExceeded => "Max-Breadth Exceeded",
            Self::BadInfoPackage => "Bad Info Package",
            Self::ConsentNeeded => "Consent Needed",
            Self::TemporarilyUnavailable => "Temporarily Unavailable",
            Self::CallTransactionDoesNotExist => "Call/Transaction Does Not Exist",
            Self::LoopDetected => "Loop Detected",
            Self::TooManyHops => "Too Many Hops",
            Self::AddressIncomplete => "Address Incomplete",
            Self::Ambiguous => "Ambiguous",
            Self::BusyHere => "Busy Here",
            Self::RequestTerminated => "Request Terminated",
            Self::NotAcceptableHere => "Not Acceptable Here",
            Self::BadEvent => "Bad Event",
            Self::RequestPending => "Request Pending",
            Self::Undecipherable => "Undecipherable",
            Self::SecurityAgreementRequired => "Security Agreement Required",
            Self::ServerInternalError => "Server Internal Error",
            Self::NotImplemented => "Not Implemented",
            Self::BadGateway => "Bad Gateway",
            Self::ServiceUnavailable => "Service Unavailable",
            Self::ServerTimeOut => "Server Time-out",
            Self::VersionNotSupported => "Version Not Supported",
            Self::MessageTooLarge => "Message Too Large",
            Self::PreconditionFailure => "Precondition Failure",
            Self::BusyEverywhere => "Busy Everywhere",
            Self::Decline => "Decline",
            Self::DoesNotExistAnywhere => "Does Not Exist Anywhere",
            Self::NotAcceptableGlobal => "Not Acceptable",
            Self::Unwanted => "Unwanted",
            Self::Other(_, reason) => reason.as_str(),
        }
    }

    fn from_code(code: u16, reason: &str) -> Self {
        match code {
            100 => Self::Trying,
            180 => Self::Ringing,
            181 => Self::CallIsBeingForwarded,
            182 => Self::Queued,
            183 => Self::SessionProgress,
            199 => Self::EarlyDialogTerminated,
            200 => Self::OK,
            202 => Self::Accepted,
            204 => Self::NoNotification,
            300 => Self::MultipleChoices,
            301 => Self::MovedPermanently,
            302 => Self::MovedTemporarily,
            305 => Self::UseProxy,
            380 => Self::AlternativeService,
            400 => Self::BadRequest,
            401 => Self::Unauthorized,
            402 => Self::PaymentRequired,
            403 => Self::Forbidden,
            404 => Self::NotFound,
            405 => Self::MethodNotAllowed,
            406 => Self::NotAcceptable,
            407 => Self::ProxyAuthenticationRequired,
            408 => Self::RequestTimeout,
            409 => Self::Conflict,
            410 => Self::Gone,
            411 => Self::LengthRequired,
            412 => Self::ConditionalRequestFailed,
            413 => Self::RequestEntityTooLarge,
            414 => Self::RequestUriTooLong,
            415 => Self::UnsupportedMediaType,
            416 => Self::UnsupportedUriScheme,
            417 => Self::UnknownResourcePriority,
            420 => Self::BadExtension,
            421 => Self::ExtensionRequired,
            422 => Self::SessionIntervalTooSmall,
            423 => Self::IntervalTooBrief,
            424 => Self::BadLocationInformation,
            428 => Self::UseIdentityHeader,
            429 => Self::ProvideReferrerIdentity,
            433 => Self::AnonymityDisallowed,
            436 => Self::BadIdentityInfo,
            437 => Self::UnsupportedCertificate,
            438 => Self::InvalidIdentityHeader,
            439 => Self::FirstHopLacksOutboundSupport,
            440 => Self::MaxBreadthExceeded,
            469 => Self::BadInfoPackage,
            470 => Self::ConsentNeeded,
            480 => Self::TemporarilyUnavailable,
            481 => Self::CallTransactionDoesNotExist,
            482 => Self::LoopDetected,
            483 => Self::TooManyHops,
            484 => Self::AddressIncomplete,
            485 => Self::Ambiguous,
            486 => Self::BusyHere,
            487 => Self::RequestTerminated,
            488 => Self::NotAcceptableHere,
            489 => Self::BadEvent,
            491 => Self::RequestPending,
            493 => Self::Undecipherable,
            494 => Self::SecurityAgreementRequired,
            500 => Self::ServerInternalError,
            501 => Self::NotImplemented,
            502 => Self::BadGateway,
            503 => Self::ServiceUnavailable,
            504 => Self::ServerTimeOut,
            505 => Self::VersionNotSupported,
            513 => Self::MessageTooLarge,
            580 => Self::PreconditionFailure,
            600 => Self::BusyEverywhere,
            603 => Self::Decline,
            604 => Self::DoesNotExistAnywhere,
            606 => Self::NotAcceptableGlobal,
            607 => Self::Unwanted,
            c => Self::Other(c, reason.to_string()),
        }
    }
}

impl From<u16> for StatusCode {
    fn from(code: u16) -> Self {
        Self::from_code(code, "Other")
    }
}

impl From<StatusCode> for u16 {
    fn from(s: StatusCode) -> u16 {
        s.code()
    }
}

impl std::convert::TryFrom<(u16, &str)> for StatusCode {
    type Error = Error;
    fn try_from((code, reason): (u16, &str)) -> Result<Self, Self::Error> {
        Ok(Self::from_code(code, reason))
    }
}

impl std::convert::TryFrom<(&str, &str)> for StatusCode {
    type Error = Error;
    fn try_from((code_str, reason): (&str, &str)) -> Result<Self, Self::Error> {
        let code: u16 = code_str.trim().parse()?;
        Ok(Self::from_code(code, reason))
    }
}

impl std::fmt::Display for StatusCode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} {}", self.code(), self.text())
    }
}

#[cfg(test)]
mod tests {
    use super::StatusCode;

    #[test]
    fn accepted_status_code_is_202() {
        assert_eq!(StatusCode::Accepted.code(), 202);
        assert_eq!(
            StatusCode::try_from((202_u16, "Accepted")).unwrap(),
            StatusCode::Accepted
        );
    }
}

#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
pub enum StatusCodeKind {
    Provisional,
    Successful,
    Redirection,
    RequestFailure,
    ServerFailure,
    GlobalFailure,
    Other,
}
