use super::attributes::*;
use crate::model::{ArgMetadata, ArgPaymentMetadata, MethodArgument};

pub fn extract_method_args(m: &syn::TraitItemFn) -> Vec<MethodArgument> {
    if m.sig.inputs.is_empty() {
        missing_self_panic(m);
    }

    let mut receiver_processed = false;
    m.sig
        .inputs
        .iter()
        .filter_map(|arg| match arg {
            syn::FnArg::Receiver(ref selfref) => {
                if selfref.mutability.is_some() || receiver_processed {
                    missing_self_panic(m);
                }
                receiver_processed = true;
                None
            },
            syn::FnArg::Typed(pat_typed) => {
                if !receiver_processed {
                    missing_self_panic(m);
                }

                Some(extract_method_arg(pat_typed))
            },
        })
        .collect()
}

fn missing_self_panic(m: &syn::TraitItemFn) -> ! {
    panic!(
        "Trait method `{}` must have `&self` as its first argument.",
        m.sig.ident
    )
}

fn extract_method_arg(pat_typed: &syn::PatType) -> MethodArgument {
    let pat = &*pat_typed.pat;
    let ty = &*pat_typed.ty;
    let mut arg_metadata = ArgMetadata::default();
    let mut unprocessed_attributes = Vec::new();

    process_arg_attributes(
        &pat_typed.attrs,
        &mut arg_metadata,
        &mut unprocessed_attributes,
    );

    let original_pat = pat.clone();
    let mut cleaned_pat = original_pat.clone();
    if let syn::Pat::Ident(ident) = &mut cleaned_pat {
        ident.mutability = None;
    }

    MethodArgument {
        original_pat,
        pat: cleaned_pat,
        ty: ty.clone(),
        unprocessed_attributes,
        metadata: arg_metadata,
    }
}

fn process_arg_attributes(
    attrs: &[syn::Attribute],
    arg_metadata: &mut ArgMetadata,
    unprocessed_attributes: &mut Vec<syn::Attribute>,
) {
    for attr in attrs {
        let processed = process_arg_attribute(attr, arg_metadata);
        if !processed {
            unprocessed_attributes.push(attr.clone());
        }
    }
}

fn process_arg_attribute(attr: &syn::Attribute, arg_metadata: &mut ArgMetadata) -> bool {
    process_payment_token_attribute(attr, arg_metadata)
        || process_payment_nonce_attribute(attr, arg_metadata)
        || process_payment_amount_attribute(attr, arg_metadata)
        || process_payment_multi_attribute(attr, arg_metadata)
        || process_callback_result_attribute(attr, arg_metadata)
        || process_event_topic_attribute(attr, arg_metadata)
}

fn check_no_other_payment_attr(arg_metadata: &ArgMetadata) {
    assert!(!arg_metadata.payment.is_payment_arg(), "Can only annotate argument with one of the following attributes: `#[payment_token]`, `#[payment_nonce]` or `#[payment_amount]`/`#[payment]`.");
}

fn process_payment_token_attribute(attr: &syn::Attribute, arg_metadata: &mut ArgMetadata) -> bool {
    let has_attr = is_payment_token(attr);
    if has_attr {
        check_no_other_payment_attr(&*arg_metadata);
        arg_metadata.payment = ArgPaymentMetadata::PaymentToken;
    }
    has_attr
}

fn process_payment_nonce_attribute(attr: &syn::Attribute, arg_metadata: &mut ArgMetadata) -> bool {
    let has_attr = is_payment_nonce(attr);
    if has_attr {
        check_no_other_payment_attr(&*arg_metadata);
        arg_metadata.payment = ArgPaymentMetadata::PaymentNonce;
    }
    has_attr
}

fn process_payment_amount_attribute(attr: &syn::Attribute, arg_metadata: &mut ArgMetadata) -> bool {
    let has_attr = is_payment_amount(attr);
    if has_attr {
        check_no_other_payment_attr(&*arg_metadata);
        arg_metadata.payment = ArgPaymentMetadata::PaymentAmount;
    }
    has_attr
}

fn process_payment_multi_attribute(attr: &syn::Attribute, arg_metadata: &mut ArgMetadata) -> bool {
    let has_attr = is_payment_multi(attr);
    if has_attr {
        check_no_other_payment_attr(&*arg_metadata);
        arg_metadata.payment = ArgPaymentMetadata::PaymentMulti;
    }
    has_attr
}

fn process_callback_result_attribute(
    attr: &syn::Attribute,
    arg_metadata: &mut ArgMetadata,
) -> bool {
    let has_attr = is_callback_result_arg(attr);
    if has_attr {
        arg_metadata.callback_call_result = true;
    }
    has_attr
}

fn process_event_topic_attribute(attr: &syn::Attribute, arg_metadata: &mut ArgMetadata) -> bool {
    let has_attr = is_event_topic(attr);
    if has_attr {
        arg_metadata.event_topic = true;
    }
    has_attr
}
