use anyhow::anyhow;
use derive_more::From;
use quick_xml::events::Event;
use quick_xml::name::{Namespace, ResolveResult};

use lavina_core::prelude::*;
use proto_xmpp::bind::BindRequest;
use proto_xmpp::client::{Iq, Message, Presence};
use proto_xmpp::disco::{InfoQuery, ItemQuery};
use proto_xmpp::mam::MessageArchiveRequest;
use proto_xmpp::roster::RosterQuery;
use proto_xmpp::session::Session;
use proto_xmpp::xml::*;

#[derive(PartialEq, Eq, Debug, From)]
pub enum IqClientBody {
    Bind(BindRequest),
    Session(Session),
    Roster(RosterQuery),
    DiscoInfo(InfoQuery),
    DiscoItem(ItemQuery),
    MessageArchiveRequest(MessageArchiveRequest),
    Unknown(Ignore),
}

impl FromXml for IqClientBody {
    type P = impl Parser<Output = Result<Self>>;

    fn parse() -> Self::P {
        |(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
            let bytes = match event {
                Event::Start(bytes) => bytes,
                Event::Empty(bytes) => bytes,
                _ => return Err(anyhow!("Unexpected XML event: {event:?}")),
            };
            let name = bytes.name();
            match_parser!(name, namespace, event;
                BindRequest,
                Session,
                RosterQuery,
                InfoQuery,
                ItemQuery,
                MessageArchiveRequest,
                {
                    delegate_parsing!(Ignore, namespace, event).into()
                }
            )
        }
    }
}

#[derive(PartialEq, Eq, Debug, From)]
pub enum ClientPacket {
    Iq(Iq<IqClientBody>),
    Message(Message<Ignore>),
    Presence(Presence<Ignore>),
    StreamEnd,
    Eos,
}

impl FromXml for ClientPacket {
    type P = impl Parser<Output = Result<Self>>;

    fn parse() -> Self::P {
        |(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
            match event {
                Event::Start(bytes) | Event::Empty(bytes) => {
                    let name = bytes.name();
                    match_parser!(name, namespace, event;
                        Iq::<IqClientBody>,
                        Presence::<Ignore>,
                        Message::<Ignore>,
                        {
                            Err(anyhow!(
                                "Unexpected XML event of name {:?} in namespace {:?}",
                                name,
                                namespace
                            ))
                        }
                    )
                }
                Event::End(bytes) => {
                    let name = bytes.name();
                    if name.local_name().as_ref() == b"stream" {
                        return Ok(ClientPacket::StreamEnd);
                    } else {
                        return Err(anyhow!("Unexpected XML event: {event:?}"));
                    }
                }
                Event::Eof => Ok(ClientPacket::Eos),
                _ => {
                    return Err(anyhow!("Unexpected XML event: {event:?}"));
                }
            }
        }
    }
}