//! Proc macros for cfgmatic configuration framework.
//!
//! This crate provides derive macros for configuration management:
//!
//! - `Config` - Derive configuration with environment variable support
//! - `ConfigEnv` - Define environment variable mappings

#![warn(missing_docs)]

use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, parse_macro_input};

/// Derive macro for configuration structs.
///
/// This macro generates:
/// - Environment variable loading
/// - Validation integration
/// - Default value support
///
/// # Example
///
/// ```ignore
/// use cfgmatic::Config;
///
/// #[derive(Config)]
/// #[config(prefix = "MYAPP")]
/// struct AppConfig {
///     #[config(env = "PORT", default = "8080")]
///     port: u16,
///     #[config(env = "HOST", default = "localhost")]
///     host: String,
/// }
/// ```
#[proc_macro_derive(Config, attributes(config))]
pub fn derive_config(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let expanded = impl_config(&input);
    TokenStream::from(expanded)
}

fn impl_config(input: &DeriveInput) -> proc_macro2::TokenStream {
    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    // Get struct-level attributes
    let struct_attrs = parse_struct_attrs(&input.attrs);
    let prefix = struct_attrs.prefix.unwrap_or_default();

    // Validate that this is a struct with named fields
    let fields_impl = match &input.data {
        Data::Struct(data) => {
            // Check for named fields
            match &data.fields {
                Fields::Named(_) => generate_fields(&data.fields, &prefix),
                Fields::Unnamed(_) => {
                    return syn::Error::new_spanned(
                        &data.fields,
                        "Config cannot be derived for tuple structs; use a struct with named fields",
                    )
                    .to_compile_error();
                }
                Fields::Unit => {
                    return syn::Error::new_spanned(
                        &data.fields,
                        "Config cannot be derived for unit structs; use a struct with named fields",
                    )
                    .to_compile_error();
                }
            }
        }
        _ => {
            return syn::Error::new_spanned(input, "Config can only be derived for structs")
                .to_compile_error();
        }
    };

    let env_prefix = if prefix.is_empty() {
        quote! { "" }
    } else {
        quote! { #prefix }
    };

    quote! {
        impl #impl_generics cfgmatic::ConfigFields for #name #ty_generics #where_clause {
            fn load_from_env(&mut self, prefix: Option<&str>) -> Result<(), cfgmatic::ConfigError> {
                let env_prefix = prefix.map(String::from).unwrap_or_else(|| #env_prefix.to_string());
                #fields_impl
                Ok(())
            }

            fn env_prefix() -> &'static str {
                #env_prefix
            }
        }
    }
}

#[derive(Default)]
struct StructAttrs {
    prefix: Option<String>,
}

fn parse_struct_attrs(attrs: &[syn::Attribute]) -> StructAttrs {
    let mut result = StructAttrs::default();

    for attr in attrs {
        if !attr.path().is_ident("config") {
            continue;
        }

        let _ = attr.parse_nested_meta(|meta| {
            if meta.path.is_ident("prefix") {
                let value: syn::LitStr = meta.value()?.parse()?;
                result.prefix = Some(value.value());
            }
            Ok(())
        });
    }

    result
}

fn generate_fields(fields: &Fields, struct_prefix: &str) -> proc_macro2::TokenStream {
    let field_impls = fields.iter().filter_map(|field| {
        let Some(field_name) = field.ident.as_ref() else {
            return None; // Should not happen due to validation above
        };
        let field_name_str = field_name.to_string();
        let attrs = parse_field_attrs(&field.attrs);

        // Skip fields without #[config(...)] attribute
        let has_config_attr = field
            .attrs
            .iter()
            .any(|attr| attr.path().is_ident("config"));
        if !has_config_attr {
            return None;
        }

        let env_var = attrs.env.unwrap_or_else(|| {
            // NPM-style env variable: PREFIX__NESTED__FIELD (double underscore as separator)
            if struct_prefix.is_empty() {
                field_name_str.to_uppercase()
            } else {
                format!(
                    "{}__{}",
                    struct_prefix.to_uppercase(),
                    field_name_str.to_uppercase()
                )
            }
        });

        // Note: The `validate` attribute is currently a marker for future functionality.
        // For now, use the validator crate's derive macro on the struct level:
        // #[derive(Validate)] with #[validate] attributes on fields.
        // See https://docs.rs/validator/latest/validator/ for details.

        let env_load = attrs.default.map_or_else(
            || {
                // Without default - only load if env var exists
                quote! {
                    if let Ok(val) = std::env::var(&env_var) {
                        self.#field_name = val.parse()
                            .map_err(|e| cfgmatic::ConfigError::Parse(
                                format!("Failed to parse {}: {}", env_var, e)
                            ))?;
                    }
                }
            },
            |default| {
                // With default value
                quote! {
                    if let Ok(val) = std::env::var(&env_var) {
                        self.#field_name = val.parse()
                            .map_err(|e| cfgmatic::ConfigError::Parse(
                                format!("Failed to parse {}: {}", env_var, e)
                            ))?;
                    } else {
                        self.#field_name = #default.parse()
                            .map_err(|e| cfgmatic::ConfigError::Parse(
                                format!("Failed to parse default for {}: {}", env_var, e)
                            ))?;
                    }
                }
            },
        );

        Some(quote! {
            {
                let env_var = if env_prefix.is_empty() {
                    #env_var.to_string()
                } else {
                    // When custom prefix is provided, use it instead of struct prefix
                    format!("{}__{}", env_prefix, #field_name_str.to_uppercase())
                };
                #env_load
            }
        })
    });

    quote! {
        #( #field_impls )*
    }
}

#[derive(Default)]
struct FieldAttrs {
    env: Option<String>,
    default: Option<String>,
    validate: bool,
}

fn parse_field_attrs(attrs: &[syn::Attribute]) -> FieldAttrs {
    let mut result = FieldAttrs::default();

    for attr in attrs {
        if !attr.path().is_ident("config") {
            continue;
        }

        let _ = attr.parse_nested_meta(|meta| {
            if meta.path.is_ident("env") {
                let value: syn::LitStr = meta.value()?.parse()?;
                result.env = Some(value.value());
            } else if meta.path.is_ident("default") {
                let value: syn::LitStr = meta.value()?.parse()?;
                result.default = Some(value.value());
            } else if meta.path.is_ident("validate") {
                result.validate = true;
            }
            Ok(())
        });
    }

    result
}

/// Derive macro for environment variable configuration.
///
/// Generates a static mapping of field names to environment variables.
/// Only fields with `#[config_env(...)]` attribute are included.
///
/// # Example
///
/// ```ignore
/// use cfgmatic::ConfigEnv;
///
/// #[derive(ConfigEnv)]
/// #[config_env(prefix = "MYAPP")]
/// struct AppEnv {
///     #[config_env(name = "SERVER_PORT")]
///     port: u16,
/// }
/// ```
#[proc_macro_derive(ConfigEnv, attributes(config_env))]
pub fn derive_config_env(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let expanded = impl_config_env(&input);
    TokenStream::from(expanded)
}

fn impl_config_env(input: &DeriveInput) -> proc_macro2::TokenStream {
    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    // Validate that this is a struct with named fields
    let (env_mappings, struct_prefix) = if let Data::Struct(data) = &input.data {
        let struct_attrs = parse_env_struct_attrs(&input.attrs);
        let prefix = struct_attrs.prefix.unwrap_or_default();

        // Check for named fields
        let mappings = match &data.fields {
            Fields::Named(_) => generate_env_mappings(&data.fields, &prefix),
            Fields::Unnamed(_) => {
                let error = syn::Error::new_spanned(
                    &data.fields,
                    "ConfigEnv cannot be derived for tuple structs; use a struct with named fields",
                );
                return error.to_compile_error();
            }
            Fields::Unit => {
                let error = syn::Error::new_spanned(
                    &data.fields,
                    "ConfigEnv cannot be derived for unit structs; use a struct with named fields",
                );
                return error.to_compile_error();
            }
        };
        (mappings, prefix)
    } else {
        let error = syn::Error::new_spanned(input, "ConfigEnv can only be derived for structs");
        return error.to_compile_error();
    };

    let env_prefix = if struct_prefix.is_empty() {
        quote! { "" }
    } else {
        quote! { #struct_prefix }
    };

    quote! {
        impl #impl_generics cfgmatic::EnvConfig for #name #ty_generics #where_clause {
            fn env_mappings() -> Vec<(&'static str, &'static str)> {
                vec![#env_mappings]
            }

            fn env_prefix() -> &'static str {
                #env_prefix
            }
        }
    }
}

#[derive(Default)]
struct EnvStructAttrs {
    prefix: Option<String>,
}

fn parse_env_struct_attrs(attrs: &[syn::Attribute]) -> EnvStructAttrs {
    let mut result = EnvStructAttrs::default();

    for attr in attrs {
        if !attr.path().is_ident("config_env") {
            continue;
        }

        let _ = attr.parse_nested_meta(|meta| {
            if meta.path.is_ident("prefix") {
                let value: syn::LitStr = meta.value()?.parse()?;
                result.prefix = Some(value.value());
            }
            Ok(())
        });
    }

    result
}

fn generate_env_mappings(fields: &Fields, struct_prefix: &str) -> proc_macro2::TokenStream {
    let mappings = fields.iter().filter_map(|field| {
        let field_name = field.ident.as_ref()?;
        let field_name_str = field_name.to_string();

        // Only include fields with #[config_env(...)] attribute
        let has_config_env_attr = field
            .attrs
            .iter()
            .any(|attr| attr.path().is_ident("config_env"));
        if !has_config_env_attr {
            return None;
        }

        let attrs = parse_env_field_attrs(&field.attrs);

        let env_var = attrs.name.unwrap_or_else(|| {
            if struct_prefix.is_empty() {
                field_name_str.to_uppercase()
            } else {
                format!(
                    "{}__{}",
                    struct_prefix.to_uppercase(),
                    field_name_str.to_uppercase()
                )
            }
        });

        Some(quote! {
            (#field_name_str, #env_var)
        })
    });

    quote! {
        #( #mappings ),*
    }
}

#[derive(Default)]
struct EnvFieldAttrs {
    name: Option<String>,
}

fn parse_env_field_attrs(attrs: &[syn::Attribute]) -> EnvFieldAttrs {
    let mut result = EnvFieldAttrs::default();

    for attr in attrs {
        if !attr.path().is_ident("config_env") {
            continue;
        }

        let _ = attr.parse_nested_meta(|meta| {
            if meta.path.is_ident("name") {
                let value: syn::LitStr = meta.value()?.parse()?;
                result.name = Some(value.value());
            }
            Ok(())
        });
    }

    result
}

/// Attribute macro for marking validation functions.
///
/// # Example
///
/// ```ignore
/// use cfgmatic::validate;
///
/// #[validate]
/// fn validate_port(port: u16) -> Result<(), String> {
///     if port > 0 && port < 65536 {
///         Ok(())
///     } else {
///         Err("Port must be between 1 and 65535".to_string())
///     }
/// }
/// ```
#[proc_macro_attribute]
pub fn validate(_args: TokenStream, input: TokenStream) -> TokenStream {
    // For now, just pass through - this is a marker attribute
    // Full implementation would integrate with validator crate
    input
}
