use darling::ast::Data;
use darling::{FromDeriveInput, FromField};
use quote::{format_ident, quote};
use syn::{Attribute, DeriveInput, ExprPath, Ident, Result, Type, Visibility};

use crate::snake_case;

#[derive(FromDeriveInput)]
#[darling(supports(struct_named), forward_attrs)]
struct DbSettingsInput {
    ident: Ident,
    vis: Visibility,
    attrs: Vec<Attribute>,
    data: Data<darling::util::Ignored, SettingField>,
}

#[derive(FromField)]
#[darling(attributes(setting), forward_attrs)]
struct SettingField {
    ident: Option<Ident>,
    ty: Type,
    attrs: Vec<Attribute>,
    #[darling(default)]
    json: bool,
    #[darling(default, rename = "default")]
    default_value: Option<String>,
    #[darling(default)]
    validate_with: Option<ExprPath>,
}

struct FieldMeta {
    ident: Ident,
    ty: Type,
    db_key: String,
    json: bool,
    default_lit: Option<String>,
    validate_with: Option<ExprPath>,
    forwarded_attrs: Vec<Attribute>,
}

/// Attribute macro for settings structs stored as key-value rows in the DB.
///
/// Each field maps to one row keyed by the field name in `snake_case`. The
/// macro generates all the boilerplate needed to load and persist those rows
/// so the call-site only declares the fields and their defaults.
///
/// # Field attributes
///
/// - `#[setting(json)]` — serialize/deserialize via `serde_json`. Required for
///   any non-`String` type such as `Vec<T>` or a custom struct.
/// - `#[setting(default = "...")]` — string literal used as the default when
///   the key is absent from the DB. For `json` fields this must be valid JSON.
///   Omitting `default` falls back to `Default::default()` for the field type.
///
/// # Generated items
///
/// - The struct itself with `#[derive(Debug, Clone)]` added.
/// - `impl Default` constructed from the per-field defaults above.
/// - `<Ident>Update` — identical fields wrapped in `Option<T>`, derives `Debug`
///   and `Default`.
/// - `pub async fn load(db: &DatabaseConnection) -> CoreResult<Self>`
/// - `pub async fn update(db: &DatabaseConnection, patch: <Ident>Update) ->
///   CoreResult<Self>`
///
/// # Example
///
/// ```rust,ignore
/// #[db_settings]
/// pub struct Settings {
///     #[setting(json, default = r#"[]"#)]
///     pub movie_paths: Vec<String>,
///     #[setting(default = "0 0 */6 * * *")]
///     pub scan_cron: String,
///     pub assrt_token: String,
/// }
/// ```
pub fn expand_db_settings(input: &DeriveInput) -> Result<proc_macro2::TokenStream> {
    let parsed = DbSettingsInput::from_derive_input(input)?;

    let struct_ident = &parsed.ident;
    let vis = &parsed.vis;
    let update_ident = format_ident!("{struct_ident}Update");

    let fields_data = parsed.data.take_struct().unwrap();
    let fields = fields_data.fields;

    let field_metas: Vec<FieldMeta> = fields
        .iter()
        .map(|f| {
            let ident = f.ident.clone().unwrap();
            let db_key = snake_case(&ident.to_string());

            FieldMeta {
                ident,
                ty: f.ty.clone(),
                db_key,
                json: f.json,
                default_lit: f.default_value.clone(),
                validate_with: f.validate_with.clone(),
                forwarded_attrs: f
                    .attrs
                    .iter()
                    .filter(|a| !a.path().is_ident("setting"))
                    .cloned()
                    .collect(),
            }
        })
        .collect();

    let struct_fields = field_metas.iter().map(|f| {
        let id = &f.ident;
        let ty = &f.ty;
        let forwarded_attrs = &f.forwarded_attrs;
        quote! {
            #( #forwarded_attrs )*
            pub #id: #ty
        }
    });

    let default_fields = field_metas.iter().map(|f| {
        let id = &f.ident;
        let ty = &f.ty;
        if let Some(ref lit) = f.default_lit {
            if f.json {
                quote! {
                    #id: ::serde_json::from_str(#lit).unwrap_or_default()
                }
            } else {
                quote! {
                    #id: #lit.to_string().parse::<#ty>().unwrap_or_default()
                }
            }
        } else {
            quote! { #id: <#ty as ::std::default::Default>::default() }
        }
    });

    let update_fields = field_metas.iter().map(|f| {
        let id = &f.ident;
        let ty = &f.ty;
        let forwarded_attrs = &f.forwarded_attrs;

        quote! {
            #( #forwarded_attrs )*
            pub #id: Option<#ty>
        }
    });

    let all_keys: Vec<&str> = field_metas.iter().map(|f| f.db_key.as_str()).collect();

    let load_field_exprs = field_metas.iter().map(|f| {
        let id = &f.ident;
        let key = &f.db_key;
        let ty = &f.ty;
        if f.json {
            quote! {
                #id: kv_map.get(#key)
                    .map(|s| ::serde_json::from_str::<#ty>(s).unwrap_or_else(|_| defaults.#id.clone()))
                    .unwrap_or_else(|| defaults.#id)
            }
        } else {
            quote! {
                #id: kv_map.get(#key)
                    .map(|s| s.parse::<#ty>().unwrap_or_else(|_| defaults.#id.clone()))
                    .unwrap_or_else(|| defaults.#id)
            }
        }
    });

    let update_push_arms = field_metas.iter().map(|f| {
        let id = &f.ident;
        let key = &f.db_key;
        if f.json {
            quote! {
                if let Some(v) = patch.#id {
                    kv_pairs.push((
                        #key,
                        ::serde_json::to_string(&v).unwrap_or_else(|_| "null".to_string()),
                    ));
                }
            }
        } else {
            quote! {
                if let Some(v) = patch.#id {
                    kv_pairs.push((#key, v.to_string()));
                }
            }
        }
    });

    let validate_arms = field_metas.iter().filter_map(|f| {
        let id = &f.ident;
        let validator = f.validate_with.as_ref()?;

        Some(quote! {
            if let Some(value) = &self.#id {
                #validator(value)?;
            }
        })
    });

    let non_setting_struct_attrs: Vec<_> = parsed
        .attrs
        .iter()
        .filter(|a| !a.path().is_ident("db_settings"))
        .collect();

    Ok(quote! {
        #[derive(Debug, Clone)]
        #( #non_setting_struct_attrs )*
        #vis struct #struct_ident {
            #( #struct_fields, )*
        }

        impl Default for #struct_ident {
            fn default() -> Self {
                Self {
                    #( #default_fields, )*
                }
            }
        }

        #[derive(Debug, Default, ::serde::Deserialize, ::salvo::oapi::ToSchema)]
        #vis struct #update_ident {
            #( #update_fields, )*
        }

        impl Validate for #update_ident {
            fn validate(&self) -> crate::error::CoreResult<()> {
                #( #validate_arms )*

                Ok(())
            }
        }

        pub async fn load(db: &::sea_orm::DatabaseConnection) -> crate::error::CoreResult<#struct_ident> {
            let defaults = #struct_ident::default();
            let kv_map = crate::db::kv::get_many(db, &[#( #all_keys ),*]).await?;

            Ok(#struct_ident {
                #( #load_field_exprs, )*
            })
        }

        pub async fn update(
            db: &::sea_orm::DatabaseConnection,
            patch: #update_ident,
        ) -> crate::error::CoreResult<#struct_ident> {
            let mut kv_pairs: Vec<(&str, String)> = vec![];
            #( #update_push_arms )*
            crate::db::kv::set_many(db, kv_pairs).await?;

            load(db).await
        }
    })
}
