use std::iter;

use proc_macro2::TokenTree;
use quote::{format_ident, quote};
use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitStr, parse_macro_input};

use proc_macro::TokenStream;

macro_rules! derive_parse {(
    @derive_only
    $( #[$attr:meta] )*
    $pub:vis
    struct $StructName:ident {
        $(
            $( #[$field_attr:meta] )*
            $field_pub:vis
            $field_name:ident : $FieldTy:ty
        ),* $(,)?
    }
) => (
    impl Parse for $StructName {
        fn parse (input: ParseStream)
          -> ::syn::Result<Self>
        {
            mod kw {
                $(
                    ::syn::custom_keyword!( $field_name );
                )*
            }
            use ::core::ops::Not as _;

            $(
                let mut $field_name = ::core::option::Option::None::< $FieldTy >;
            )*
            while input.is_empty().not() {
                let lookahead = input.lookahead1();
                match () {
                  $(
                    _case if lookahead.peek(kw::$field_name) => {
                        let span = input.parse::<kw::$field_name>().unwrap().span;
                        let _: ::syn::Token![ = ] = input.parse()?;
                        let prev = $field_name.replace(input.parse()?);
                        if prev.is_some() {
                            return ::syn::Result::Err(::syn::Error::new(span, "Duplicate key"));
                        }
                    },
                  )*
                    _default => return ::syn::Result::Err(lookahead.error()),
                }
                let _: ::core::option::Option<::syn::Token![ , ]> = input.parse()?;
            }
            Ok(Self {
                $(
                    $field_name: $field_name.ok_or_else(|| ::syn::Error::new(
                        ::proc_macro2::Span::call_site(),
                        ::core::concat!("Missing key `", ::core::stringify!($field_name), "`"),
                    ))?,
                )*
            })
        }
    }
); (
    $( #[$attr:meta] )* $pub:vis struct $($rest:tt)*
) => (
    $( #[$attr] )* $pub struct $($rest)*

    derive_parse! { @derive_only  $( #[$attr] )* $pub struct $($rest)* }
)}

derive_parse! {
    struct TopicArgs {
        pub_sub_name: LitStr,
        topic: LitStr
    }
}

#[proc_macro_attribute]
pub fn actor(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let actor_struct_name = match syn::parse::<syn::ItemStruct>(item.clone()) {
        Ok(actor_struct) => actor_struct.ident.clone(),
        Err(_) => match syn::parse::<syn::ItemType>(item.clone()) {
            Ok(ty) => ty.ident.clone(),
            Err(e) => panic!("Error parsing actor struct: {e}"),
        },
    };

    let mut result = TokenStream::from(quote!(
        #[async_trait::async_trait]
        impl dapr::server::actor::axum::extract::FromRequestParts<dapr::server::actor::runtime::ActorState> for &#actor_struct_name {
            type Rejection = dapr::server::actor::ActorRejection;

            async fn from_request_parts(
                parts: &mut dapr::server::actor::axum::http::request::Parts,
                state: &dapr::server::actor::runtime::ActorState,
            ) -> Result<Self, Self::Rejection> {
                let path = match dapr::server::actor::axum::extract::Path::<dapr::server::actor::ActorPath>::from_request_parts(parts, state).await {
                    Ok(path) => path,
                    Err(e) => {
                        log::error!("Error getting path: {}", e);
                        return Err(dapr::server::actor::ActorRejection::Path(e));
                    }
                };
                let actor_type = state.actor_type.clone();
                let actor_id = path.actor_id.clone();
                log::info!(
                    "Request for actor_type: {}, actor_id: {}",
                    actor_type,
                    actor_id
                );
                let actor = match state
                    .runtime
                    .get_or_create_actor(&actor_type, &actor_id)
                    .await
                {
                    Ok(actor) => actor,
                    Err(e) => {
                        log::error!("Error getting actor: {}", e);
                        return Err(dapr::server::actor::ActorRejection::ActorError(e.to_string()));
                    }
                };
                let actor = actor.as_ref();
                let well_known_actor =
                    unsafe { &*(actor as *const dyn dapr::server::actor::Actor as *const #actor_struct_name) };
                Ok(well_known_actor)
            }
        }
    ));

    result.extend(iter::once(item));

    result
}

#[proc_macro_attribute]
pub fn topic(args: TokenStream, input: TokenStream) -> TokenStream {
    let new_input = proc_macro2::TokenStream::from(input);
    let mut iter = new_input.clone().into_iter().filter(|i| match i {
        TokenTree::Group(_) => true,
        TokenTree::Ident(_) => true,
        TokenTree::Punct(_) => false,
        TokenTree::Literal(_) => false,
    });

    let mut current = iter.next().unwrap();

    while current.to_string() != "fn" {
        current = iter.next().unwrap()
    }

    let name = iter.next().unwrap();

    let struct_name = name
        .to_string()
        .split('_')
        .map(|i| {
            let mut chars: Vec<char> = i.chars().collect();
            chars[0] = chars[0].to_ascii_uppercase();
            let new_string: String = chars.into_iter().collect();
            new_string
        })
        .collect::<Vec<String>>()
        .join("");

    let name_ident = Ident::new(name.to_string().as_str(), name.span());

    let struct_name_ident = Ident::new(struct_name.as_str(), name.span());

    let vars: Vec<String> = iter
        .next()
        .unwrap()
        .to_string()
        .replace(['(', ')'], "")
        .split(':')
        .enumerate()
        .filter(|&(i, _)| i % 2 != 0)
        .map(|(_, i)| i.trim().to_string())
        .collect();

    assert_eq!(vars.len(), 1, "Expected to only have one input variable");

    let parse_statement = match vars[0] == *"String" {
        true => {
            quote! {
                let message = message.to_string();
            }
        }
        false => {
            let type_ident = format_ident!("{}", vars[0]);
            quote! {
               let message: #type_ident = dapr::serde_json::from_str(message.to_string().as_str()).unwrap();
            }
        }
    };

    let args = parse_macro_input!(args as TopicArgs);

    let topic = args.topic.value();

    let pub_sub_name = args.pub_sub_name.value();

    let tokens = quote! {
        #new_input

        #[derive(Default)]
        struct #struct_name_ident;

        #[tonic::async_trait]
        impl dapr::appcallback::HandlerMethod for #struct_name_ident {
            async fn handler(
                &self,
                request: TopicEventRequest,
            ) -> Result<tonic::Response<TopicEventResponse>, tonic::Status> {
                let data = &request.data;
                let data_content_type = &request.data_content_type;

                let message = String::from_utf8_lossy(&data);

                #parse_statement

                #name_ident(message).await;

                Ok(tonic::Response::new(TopicEventResponse::default()))
            }
        }
        impl #struct_name_ident {
            pub fn get_handler(self) -> dapr::appcallback::Handler {
                dapr::appcallback::Handler {
                    pub_sub_name: #pub_sub_name.to_string(),
                    topic: #topic.to_string(),
                    handler: Box::new(self)
                }
            }
        }
    };

    tokens.into()
}
