use std::collections::BTreeMap;

use proc_macro2::{Span, TokenStream};
use syn::{Attribute, FieldValue, Ident, parse::Parse, spanned::Spanned};

use crate::{
    capture, hook,
    util::{AttributeCfg, ExprIsLocalVariable, FieldValueKey, maybe_cfg},
};

#[derive(Debug)]
pub struct Props {
    match_value_tokens: Vec<TokenStream>,
    match_binding_tokens: Vec<TokenStream>,
    key_values: BTreeMap<String, KeyValue>,
    key_value_index: usize,
}

impl Parse for Props {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let fv = input.parse_terminated(FieldValue::parse, Token![,])?;

        let mut props = Props::new();

        for fv in fv {
            props.push(&fv, capture::default_fn_name(&fv), false, true)?;
        }

        Ok(props)
    }
}

#[derive(Debug)]
pub struct KeyValue {
    match_bound_tokens: TokenStream,
    direct_bound_tokens: TokenStream,
    raw_bound_tokens: TokenStream,
    label: Ident,
    span: Span,
    pub interpolated: bool,
    pub captured: bool,
    pub cfg_attr: Option<Attribute>,
    pub attrs: Vec<Attribute>,
}

impl KeyValue {
    pub fn span(&self) -> Span {
        self.span.clone()
    }

    pub fn hole_tokens(&self) -> TokenStream {
        let label = &self.label;
        let attrs = &self.attrs;

        quote!(#(#attrs)* #label)
    }
}

impl Props {
    pub fn new() -> Self {
        Props {
            match_value_tokens: Vec::new(),
            match_binding_tokens: Vec::new(),
            key_values: BTreeMap::new(),
            key_value_index: 0,
        }
    }

    pub fn match_input_tokens(&self) -> impl Iterator<Item = &TokenStream> {
        self.match_value_tokens.iter()
    }

    pub fn match_binding_tokens(&self) -> impl Iterator<Item = &TokenStream> {
        self.match_binding_tokens.iter()
    }

    pub fn match_bound_tokens(&self) -> TokenStream {
        Self::sorted_props_tokens(self.key_values.values().map(|kv| &kv.match_bound_tokens))
    }

    pub fn props_tokens(&self) -> TokenStream {
        Self::sorted_props_tokens(self.key_values.values().map(|kv| &kv.direct_bound_tokens))
    }

    pub fn raw_props_tokens(&self) -> Result<TokenStream, syn::Error> {
        // Make sure no key-values carry attributes
        // This is a limitation imposed while this code is only used internally
        // If we expose some way for users to produce raw props we'll need to rethink this
        for (k, v) in &self.key_values {
            if v.attrs.len() != 0 {
                return Err(syn::Error::new(
                    v.span,
                    format!("attributes on {k} are not supported when capturing directly"),
                ));
            }
        }

        Ok(match self.key_values.len() {
            0 => quote!(emit::Empty),
            1 => self
                .key_values
                .first_key_value()
                .unwrap()
                .1
                .raw_bound_tokens
                .clone(),
            _ => {
                let key_values = self.key_values.values().map(|kv| &kv.raw_bound_tokens);

                quote!(emit::__private::__PrivateTupleMacroProps::new((#(#key_values),*)))
            }
        })
    }

    fn sorted_props_tokens<'a>(
        key_values: impl Iterator<Item = &'a TokenStream> + 'a,
    ) -> TokenStream {
        quote!(emit::__private::__PrivateMacroProps::from_array([#(#key_values),*]))
    }

    fn next_match_binding_ident(&mut self, span: Span) -> Ident {
        let i = Ident::new(&format!("__tmp{}", self.key_value_index), span);
        self.key_value_index += 1;

        i
    }

    pub fn get(&self, label: &str) -> Option<&KeyValue> {
        self.key_values.get(label)
    }

    pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a KeyValue)> + 'a {
        self.key_values.iter().map(|(k, v)| (&**k, v))
    }

    pub fn push(
        &mut self,
        fv: &FieldValue,
        fn_name: TokenStream,
        interpolated: bool,
        captured: bool,
    ) -> Result<(), syn::Error> {
        let mut attrs = vec![];
        let mut cfg_attr = None;

        for attr in &fv.attrs {
            if attr.is_cfg() {
                if cfg_attr.is_some() {
                    return Err(syn::Error::new(
                        attr.span(),
                        "only a single #[cfg] is supported on key-value pairs",
                    ));
                }

                cfg_attr = Some(attr.clone());
            } else {
                attrs.push(attr.clone());
            }
        }

        let match_bound_ident = self.next_match_binding_ident(fv.span());

        let key_value_tokens = maybe_cfg(
            cfg_attr.as_ref(),
            fv.span(),
            capture::eval_key_value_with_hook(&attrs, &fv, &fn_name, interpolated, captured)?,
        );

        let direct_bound_tokens = quote_spanned!(fv.span()=> #key_value_tokens);

        let raw_bound_tokens = capture::raw_key_value(fv)?;

        // This is one of the few places we end up looking at the shape of an expression and deciding how to emit code for it.
        //
        // In the 2021 edition and prior, lifetimes of temporaries created in a `match expr` would be extended to the end of the
        // `match`. In the 2024 edition, that doesn't happen anymore. So to keep the semantics that you can capture a value in scope
        // by reference, and supply temporaries inline, we check whether the field value is a local like `x: a.b.c` and take a reference
        // inside the `match expr`, or a complex expression like `x: a.b()`, where it's taken by value in the `match expr`.
        let (match_value_tokens, match_bound_tokens) = if fv.expr.is_local_variable() {
            // If the expression is a local variable then it's an already in-scope identifier
            // We take the expression by reference in a `match`

            let match_value_tokens = quote_spanned!(fv.span()=>#key_value_tokens);
            let match_bound_tokens =
                quote_spanned!(fv.span()=>#cfg_attr (#match_bound_ident.0, #match_bound_ident.1));

            (match_value_tokens, match_bound_tokens)
        } else {
            // If the expression is not a local variable then it's constructed for the call
            // We take the expression by value in a `match`

            let key_tokens = capture::eval_key_with_hook(&attrs, &fv, interpolated, captured)?;
            let value_expr = &fv.expr;

            let match_value_tokens = maybe_cfg(
                cfg_attr.as_ref(),
                fv.span(),
                quote_spanned!(fv.span()=> (#key_tokens, #value_expr)),
            );

            let bound_value_tokens = capture::value_with_hook(
                &syn::parse_quote_spanned!(fv.span()=>#match_bound_ident.1),
                &fn_name,
                interpolated,
                captured,
            );
            let bound_value_tokens = hook::eval_hooks(
                &attrs,
                syn::parse_quote_spanned!(fv.span()=>#bound_value_tokens),
            )?;

            let match_bound_tokens = quote!(#cfg_attr (#match_bound_ident.0, #bound_value_tokens));

            (match_value_tokens, match_bound_tokens)
        };

        self.match_value_tokens.push(match_value_tokens);

        // If there's a #[cfg] then also push its reverse
        // This is to give a dummy value to the pattern binding since they don't support attributes
        if let Some(cfg_attr) = &cfg_attr {
            let cfg_attr = cfg_attr
                .invert_cfg()
                .ok_or_else(|| syn::Error::new(cfg_attr.span(), "attribute is not a #[cfg]"))?;

            self.match_value_tokens
                .push(quote_spanned!(fv.span()=> #cfg_attr ()));
        }

        self.match_binding_tokens
            .push(quote_spanned!(fv.span()=> #match_bound_ident));

        if fv.colon_token.is_some() && !captured {
            return Err(syn::Error::new(
                fv.span(),
                "uncaptured key values must be plain identifiers",
            ));
        }

        // Make sure keys aren't duplicated
        let previous = self.key_values.insert(
            fv.key_name()?,
            KeyValue {
                match_bound_tokens,
                direct_bound_tokens,
                raw_bound_tokens,
                span: fv.span(),
                label: fv.key_ident()?,
                cfg_attr,
                attrs,
                captured,
                interpolated,
            },
        );

        if previous.is_some() {
            return Err(syn::Error::new(fv.span(), "keys cannot be duplicated"));
        }

        Ok(())
    }
}

/**
Check properties for reserved keys used by event metadata.
*/
pub fn check_evt_props(props: &Props) -> Result<(), syn::Error> {
    for (k, v) in &props.key_values {
        match &**k {
            emit_core::well_known::KEY_MDL => {
                return Err(syn::Error::new(
                    v.span(),
                    "specify the module using the `mdl` control parameter before the template",
                ));
            }
            emit_core::well_known::KEY_TPL => {
                return Err(syn::Error::new(
                    v.span(),
                    "the template is specified as a string literal before properties",
                ));
            }
            emit_core::well_known::KEY_MSG => {
                return Err(syn::Error::new(
                    v.span(),
                    "the message is specified as a string literal template before properties",
                ));
            }
            emit_core::well_known::KEY_TS => {
                return Err(syn::Error::new(
                    v.span(),
                    "specify the timestamp using the `extent` control parameter before the template",
                ));
            }
            emit_core::well_known::KEY_TS_START => {
                return Err(syn::Error::new(
                    v.span(),
                    "specify the start timestamp using the `extent` control parameter before the template",
                ));
            }
            _ => (),
        }
    }

    Ok(())
}

/**
Push common properties for events.
*/
pub fn push_evt_props(props: &mut Props, level: Option<TokenStream>) -> Result<(), syn::Error> {
    // Add the level as a property
    if let Some(level_value) = level {
        let level_ident = Ident::new(emit_core::well_known::KEY_LVL, Span::call_site());

        let fv = syn::parse2::<FieldValue>(quote!(#level_ident: #level_value))?;

        props.push(&fv, capture::default_fn_name(&fv), false, true)?;
    }

    Ok(())
}
