// https://datatracker.ietf.org/doc/html/rfc6120#appendix-A.8

use peanuts::{
    element::{FromElement, IntoElement},
    Element, XML_NS,
};
use thiserror::Error;

pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-stanzas";

#[derive(Error, Clone, Debug)]
pub enum Error {
    #[error("bad request")]
    BadRequest,
    #[error("conflict")]
    Conflict,
    #[error("feature not implemented")]
    FeatureNotImplemented,
    #[error("forbidden")]
    Forbidden,
    #[error("gone: {0:?}")]
    Gone(Option<String>),
    #[error("internal server error")]
    InternalServerError,
    #[error("item not found")]
    ItemNotFound,
    #[error("JID malformed")]
    JIDMalformed,
    #[error("not acceptable")]
    NotAcceptable,
    #[error("not allowed")]
    NotAllowed,
    #[error("not authorized")]
    NotAuthorized,
    #[error("policy violation")]
    PolicyViolation,
    #[error("recipient unavailable")]
    RecipientUnavailable,
    #[error("redirect: {0:?}")]
    Redirect(Option<String>),
    #[error("registration required")]
    RegistrationRequired,
    #[error("remote server not found")]
    RemoteServerNotFound,
    #[error("remote server timeout")]
    RemoteServerTimeout,
    #[error("resource constraint")]
    ResourceConstraint,
    #[error("service unavailable")]
    ServiceUnavailable,
    #[error("subscription required")]
    SubscriptionRequired,
    #[error("undefined condition")]
    UndefinedCondition,
    #[error("unexpected request")]
    UnexpectedRequest,
}

impl FromElement for Error {
    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
        let error;
        match element.identify() {
            (Some(XMLNS), "bad-request") => error = Error::BadRequest,
            (Some(XMLNS), "conflict") => error = Error::Conflict,
            (Some(XMLNS), "feature-not-implemented") => error = Error::FeatureNotImplemented,
            (Some(XMLNS), "forbidden") => error = Error::Forbidden,
            (Some(XMLNS), "gone") => return Ok(Error::Gone(element.pop_value_opt()?)),
            (Some(XMLNS), "internal-server-error") => error = Error::InternalServerError,
            (Some(XMLNS), "item-not-found") => error = Error::ItemNotFound,
            (Some(XMLNS), "jid-malformed") => error = Error::JIDMalformed,
            (Some(XMLNS), "not-acceptable") => error = Error::NotAcceptable,
            (Some(XMLNS), "not-allowed") => error = Error::NotAllowed,
            (Some(XMLNS), "not-authorized") => error = Error::NotAuthorized,
            (Some(XMLNS), "policy-violation") => error = Error::PolicyViolation,
            (Some(XMLNS), "recipient-unavailable") => error = Error::RecipientUnavailable,
            (Some(XMLNS), "redirect") => return Ok(Error::Redirect(element.pop_value_opt()?)),
            (Some(XMLNS), "registration-required") => error = Error::RegistrationRequired,
            (Some(XMLNS), "remote-server-not-found") => error = Error::RemoteServerNotFound,
            (Some(XMLNS), "remote-server-timeout") => error = Error::RemoteServerTimeout,
            (Some(XMLNS), "resource-constraint") => error = Error::ResourceConstraint,
            (Some(XMLNS), "service-unavailable") => error = Error::ServiceUnavailable,
            (Some(XMLNS), "subscription-required") => error = Error::SubscriptionRequired,
            (Some(XMLNS), "undefined-condition") => error = Error::UndefinedCondition,
            (Some(XMLNS), "unexpected-request") => error = Error::UnexpectedRequest,
            _ => return Err(peanuts::DeserializeError::UnexpectedElement(element)),
        }
        element.no_more_content()?;
        return Ok(error);
    }
}

impl IntoElement for Error {
    fn builder(&self) -> peanuts::element::ElementBuilder {
        match self {
            Error::BadRequest => Element::builder("bad-request", Some(XMLNS)),
            Error::Conflict => Element::builder("conflict", Some(XMLNS)),
            Error::FeatureNotImplemented => {
                Element::builder("feature-not-implemented", Some(XMLNS))
            }
            Error::Forbidden => Element::builder("forbidden", Some(XMLNS)),
            Error::Gone(r) => Element::builder("gone", Some(XMLNS)).push_text_opt(r.clone()),
            Error::InternalServerError => Element::builder("internal-server-error", Some(XMLNS)),
            Error::ItemNotFound => Element::builder("item-not-found", Some(XMLNS)),
            Error::JIDMalformed => Element::builder("jid-malformed", Some(XMLNS)),
            Error::NotAcceptable => Element::builder("not-acceptable", Some(XMLNS)),
            Error::NotAllowed => Element::builder("not-allowed", Some(XMLNS)),
            Error::NotAuthorized => Element::builder("not-authorized", Some(XMLNS)),
            Error::PolicyViolation => Element::builder("policy-violation", Some(XMLNS)),
            Error::RecipientUnavailable => Element::builder("recipient-unavailable", Some(XMLNS)),
            Error::Redirect(r) => {
                Element::builder("redirect", Some(XMLNS)).push_text_opt(r.clone())
            }
            Error::RegistrationRequired => Element::builder("registration-required", Some(XMLNS)),
            Error::RemoteServerNotFound => Element::builder("remote-server-not-found", Some(XMLNS)),
            Error::RemoteServerTimeout => Element::builder("remote-server-timeout", Some(XMLNS)),
            Error::ResourceConstraint => Element::builder("resource-constraint", Some(XMLNS)),
            Error::ServiceUnavailable => Element::builder("service-unavailable", Some(XMLNS)),
            Error::SubscriptionRequired => Element::builder("subscription-required", Some(XMLNS)),
            Error::UndefinedCondition => Element::builder("undefined-condition", Some(XMLNS)),
            Error::UnexpectedRequest => Element::builder("unexpected-request", Some(XMLNS)),
        }
    }
}

#[derive(Clone, Debug)]
pub struct Text {
    lang: Option<String>,
    pub text: Option<String>,
}

impl FromElement for Text {
    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
        element.check_name("text")?;
        element.check_name(XMLNS)?;

        let lang = element.attribute_opt_namespaced("lang", XML_NS)?;
        let text = element.pop_value_opt()?;

        Ok(Text { lang, text })
    }
}

impl IntoElement for Text {
    fn builder(&self) -> peanuts::element::ElementBuilder {
        Element::builder("text", Some(XMLNS))
            .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
            .push_text_opt(self.text.clone())
    }
}