use proc_macro2::TokenStream;
use quote::{quote, format_ident};
use syn::{
    parse::{Parse, ParseStream},
    braced, Attribute, Expr, Ident, LitStr, Token, Type,
};

// ── Data structures ──

pub struct UnifiedParamsDef {
    name: Ident,
    prefix: String,
    json_file: String,
    fields: Vec<UnifiedField>,
}

#[derive(Clone, Copy, PartialEq)]
enum FieldKind {
    Required,
    Readonly,
    Mutable,
}

struct UnifiedField {
    kind: FieldKind,
    custom_env: Option<String>,
    name: Ident,
    ty: Type,
    default_value: Option<Expr>,
}

// ── Parsing ──

impl Parse for UnifiedParamsDef {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let name: Ident = input.parse()?;
        input.parse::<Token![,]>()?;
        let prefix_lit: LitStr = input.parse()?;
        let prefix = prefix_lit.value();
        input.parse::<Token![,]>()?;
        let json_file_lit: LitStr = input.parse()?;
        let json_file = json_file_lit.value();

        let content;
        braced!(content in input);

        let mut fields = Vec::new();
        while !content.is_empty() {
            fields.push(content.parse::<UnifiedField>()?);
        }

        Ok(UnifiedParamsDef { name, prefix, json_file, fields })
    }
}

impl Parse for UnifiedField {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let attrs = input.call(Attribute::parse_outer)?;
        let (kind, custom_env) = parse_field_attrs(&attrs);

        let name: Ident = input.parse()?;
        input.parse::<Token![:]>()?;
        let ty: Type = input.parse()?;

        let default_value = if input.peek(Token![=]) {
            input.parse::<Token![=]>()?;
            let expr: Expr = input.parse()?;
            Some(expr)
        } else {
            None
        };

        if input.peek(Token![,]) {
            input.parse::<Token![,]>()?;
        }

        Ok(UnifiedField { kind, custom_env, name, ty, default_value })
    }
}

fn parse_field_attrs(attrs: &[Attribute]) -> (FieldKind, Option<String>) {
    let mut kind = FieldKind::Mutable;
    let mut custom_env = None;

    for attr in attrs {
        if attr.path().is_ident("required") {
            kind = FieldKind::Required;
        } else if attr.path().is_ident("readonly") {
            kind = FieldKind::Readonly;
        } else if attr.path().is_ident("env") {
            if let Ok(value) = attr.parse_args::<LitStr>() {
                custom_env = Some(value.value());
            }
        }
    }

    (kind, custom_env)
}

// ── Template helpers ──

/// Extract `{field_name}` references from a template string.
fn extract_template_refs(template: &str) -> Vec<String> {
    let mut refs = Vec::new();
    let mut chars = template.chars().peekable();
    while let Some(ch) = chars.next() {
        if ch == '{' {
            let mut name = String::new();
            for inner in chars.by_ref() {
                if inner == '}' {
                    break;
                }
                name.push(inner);
            }
            if !name.is_empty() {
                refs.push(name);
            }
        }
    }
    refs
}

// ── Type helpers ──

fn extract_option_inner(ty: &Type) -> Option<&Type> {
    if let Type::Path(tp) = ty {
        let seg = tp.path.segments.last()?;
        if seg.ident == "Option" {
            if let syn::PathArguments::AngleBracketed(args) = &seg.arguments {
                if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
                    return Some(inner);
                }
            }
        }
    }
    None
}

fn is_string_type(ty: &Type) -> bool {
    if let Type::Path(tp) = ty {
        if let Some(seg) = tp.path.segments.last() {
            return seg.ident == "String";
        }
    }
    false
}

fn is_bool_type(ty: &Type) -> bool {
    if let Type::Path(tp) = ty {
        if let Some(seg) = tp.path.segments.last() {
            return seg.ident == "bool";
        }
    }
    false
}

fn get_env_name(field: &UnifiedField, prefix: &str) -> String {
    match &field.custom_env {
        Some(custom) => custom.clone(),
        None => format!("{}_{}", prefix, field.name.to_string().to_uppercase()),
    }
}

// ── Code generation ──

pub fn expand(def: UnifiedParamsDef) -> TokenStream {
    let struct_name = &def.name;
    let json_file_template = &def.json_file;

    // Extract template references from JSON path
    let template_refs = extract_template_refs(json_file_template);

    // Validate template refs at compile time: each must match a declared field
    let field_names_set: Vec<String> = def.fields.iter()
        .map(|f| f.name.to_string())
        .collect();
    for tref in &template_refs {
        if !field_names_set.contains(tref) {
            let msg = format!(
                "JSON path template references unknown field '{}'. Declared fields: {:?}",
                tref, field_names_set
            );
            return syn::Error::new(proc_macro2::Span::call_site(), msg)
                .to_compile_error();
        }
    }

    // --- Main struct fields ---
    let main_fields: Vec<_> = def.fields.iter().map(|f| {
        let name = &f.name;
        let ty = &f.ty;
        quote! { pub #name: #ty }
    }).collect();

    // --- JsonRepr struct (mutable fields only — readonly/required never touch JSON) ---
    let json_repr_name = format_ident!("__{}JsonRepr", struct_name);

    let json_repr_fields: Vec<_> = def.fields.iter().filter(|f| f.kind == FieldKind::Mutable).map(|f| {
        let name = &f.name;
        let is_option = extract_option_inner(&f.ty).is_some();
        if is_option {
            let ty = &f.ty;
            quote! {
                #[serde(default, skip_serializing_if = "Option::is_none")]
                pub #name: #ty
            }
        } else {
            let ty = &f.ty;
            quote! {
                #[serde(default, skip_serializing_if = "Option::is_none")]
                pub #name: Option<#ty>
            }
        }
    }).collect();

    // --- Env reading code for each field ---
    let env_names: Vec<String> = def.fields.iter()
        .map(|f| get_env_name(f, &def.prefix))
        .collect();

    let env_bindings: Vec<_> = def.fields.iter().zip(env_names.iter())
        .map(|(f, env_name)| gen_env_binding(f, env_name))
        .collect();

    // --- Merge code for each field ---
    let merge_bindings: Vec<_> = def.fields.iter()
        .map(gen_merge_binding)
        .collect();

    // --- Required checks ---
    let required_checks: Vec<_> = def.fields.iter().zip(env_names.iter())
        .filter(|(f, _)| f.kind == FieldKind::Required)
        .map(|(f, env_name)| {
            let name = &f.name;
            let name_str = name.to_string();
            let env_lit = syn::LitStr::new(env_name, proc_macro2::Span::call_site());
            let is_option = extract_option_inner(&f.ty).is_some();
            if is_option {
                let set_var = format_ident!("__env_{}_set", name);
                quote! {
                    if !#set_var {
                        __errors.push(format!("Missing required param '{}' (env: {})", #name_str, #env_lit));
                    }
                }
            } else {
                let env_var = format_ident!("__env_{}", name);
                quote! {
                    if #env_var.is_none() {
                        __errors.push(format!("Missing required param '{}' (env: {})", #name_str, #env_lit));
                    }
                }
            }
        })
        .collect();

    // --- Template expansion code ---
    let template_expansion = {
        let json_file_lit = syn::LitStr::new(json_file_template, proc_macro2::Span::call_site());
        if template_refs.is_empty() {
            // No template refs: use literal path directly
            quote! {
                let __json_path = std::path::PathBuf::from(#json_file_lit);
            }
        } else {
            // Build .replace() chain for each template ref
            let replace_calls: Vec<_> = template_refs.iter().map(|tref| {
                let placeholder = format!("{{{}}}", tref);
                let placeholder_lit = syn::LitStr::new(&placeholder, proc_macro2::Span::call_site());
                let env_var = format_ident!("__env_{}", tref);
                quote! {
                    __json_path_str = __json_path_str.replace(#placeholder_lit, &(#env_var).clone().unwrap_or_default());
                }
            }).collect();

            quote! {
                let mut __json_path_str = #json_file_lit.to_string();
                #(#replace_calls)*
                let __json_path = std::path::PathBuf::from(&__json_path_str);
            }
        }
    };

    // --- Field names for struct construction ---
    let field_names: Vec<_> = def.fields.iter().map(|f| &f.name).collect();

    // --- Getters ---
    let getters: Vec<_> = def.fields.iter().map(|f| {
        let name = &f.name;
        let ty = &f.ty;
        let is_option = extract_option_inner(ty).is_some();
        if is_string_type(ty) || is_option {
            quote! {
                pub fn #name() -> #ty {
                    Self::global().#name.clone()
                }
            }
        } else {
            quote! {
                pub fn #name() -> #ty {
                    Self::global().#name
                }
            }
        }
    }).collect();

    // --- Mutable setters ---
    let setters: Vec<_> = def.fields.iter().filter(|f| f.kind == FieldKind::Mutable).map(|f| {
        let name = &f.name;
        let ty = &f.ty;
        let setter_name = format_ident!("set_{}", name);
        quote! {
            pub fn #setter_name(val: #ty) {
                {
                    let mut guard = Self::__state().write().expect("RwLock poisoned");
                    guard.#name = val;
                }
                Self::__save_mutable();
            }
        }
    }).collect();

    // --- Save mutable: build JsonRepr with only mutable fields ---
    let save_mutable_fields: Vec<_> = def.fields.iter()
        .filter(|f| f.kind == FieldKind::Mutable)
        .map(|f| {
            let name = &f.name;
            let is_option = extract_option_inner(&f.ty).is_some();
            if is_option {
                quote! { #name: guard.#name.clone() }
            } else {
                quote! { #name: Some(guard.#name.clone()) }
            }
        })
        .collect();

    // --- Unknown env detection ---
    let known_env_names: Vec<_> = env_names.iter().map(|n| {
        syn::LitStr::new(n, proc_macro2::Span::call_site())
    }).collect();
    let prefix_underscore = format!("{}_", def.prefix);

    // --- Static names ---
    let state_name = format_ident!("__{}_STATE", struct_name.to_string().to_uppercase());
    let json_path_name = format_ident!("__{}_JSON_PATH", struct_name.to_string().to_uppercase());

    quote! {
        #[derive(Clone, Debug)]
        pub struct #struct_name {
            #(#main_fields,)*
        }

        #[derive(Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
        struct #json_repr_name {
            #(#json_repr_fields,)*
        }

        static #state_name: std::sync::OnceLock<std::sync::RwLock<#struct_name>> = std::sync::OnceLock::new();
        static #json_path_name: std::sync::OnceLock<String> = std::sync::OnceLock::new();

        impl #struct_name {
            /// Initialize from env + JSON + defaults. Call once at startup.
            /// JSON path supports `{field_name}` template references resolved from env values.
            pub fn init() {
                let mut __errors: Vec<String> = Vec::new();

                // Phase 1: Read env vars
                #(#env_bindings)*

                // Detect unknown prefix variables
                {
                    let __known: std::collections::HashSet<String> = vec![
                        #(#known_env_names.to_string(),)*
                    ].into_iter().collect();
                    let __prefix = #prefix_underscore;
                    for (__key, _) in std::env::vars() {
                        if __key.starts_with(__prefix) && !__known.contains(&__key) {
                            __errors.push(format!("Unknown env: {}", __key));
                        }
                    }
                }

                // Phase 2: Required checks
                #(#required_checks)*

                // Phase 3: Expand JSON path template using env values
                #template_expansion

                // Phase 4: Read JSON file
                let __json: #json_repr_name = match std::fs::read_to_string(&__json_path) {
                    Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
                    Err(_) => #json_repr_name::default(),
                };

                // Phase 5: Merge (env > json > default)
                #(#merge_bindings)*

                if !__errors.is_empty() {
                    panic!("\nParameter errors:\n{}", __errors.join("\n"));
                }

                // Phase 6: Set global state
                #json_path_name.set(__json_path.to_string_lossy().into_owned()).ok();
                #state_name.set(std::sync::RwLock::new(#struct_name {
                    #(#field_names,)*
                })).ok();
            }

            fn __state() -> &'static std::sync::RwLock<#struct_name> {
                #state_name.get().expect("unified params not initialized, call init() first")
            }

            /// Get read access to global params.
            pub fn global() -> std::sync::RwLockReadGuard<'static, #struct_name> {
                Self::__state().read().expect("RwLock poisoned")
            }

            // --- Getters (convenience, clone values) ---
            #(#getters)*

            // --- Mutable setters ---
            #(#setters)*

            /// Modify params in-place (for post-init processing).
            pub fn modify(f: impl FnOnce(&mut Self)) {
                let mut guard = Self::__state().write().expect("RwLock poisoned");
                f(&mut *guard);
            }

            fn __save_mutable() {
                let guard = Self::__state().read().expect("RwLock poisoned");
                let repr = #json_repr_name {
                    #(#save_mutable_fields,)*
                };
                drop(guard);

                if let Some(json_path_str) = #json_path_name.get() {
                    let path = std::path::Path::new(json_path_str.as_str());
                    if let Some(parent) = path.parent() {
                        let _ = std::fs::create_dir_all(parent);
                    }
                    if let Ok(json) = serde_json::to_string_pretty(&repr) {
                        let tmp_path = path.with_extension("tmp");
                        if std::fs::write(&tmp_path, &json).is_ok() {
                            let _ = std::fs::rename(&tmp_path, path);
                        }
                    }
                }
            }
        }
    }
}

// ── Env binding generators ──

fn gen_env_binding(field: &UnifiedField, env_name: &str) -> TokenStream {
    let var_name = format_ident!("__env_{}", field.name);
    let env_lit = syn::LitStr::new(env_name, proc_macro2::Span::call_site());
    let is_option = extract_option_inner(&field.ty).is_some();

    if is_option {
        let set_var = format_ident!("__env_{}_set", field.name);
        let inner_ty = extract_option_inner(&field.ty).unwrap();
        if is_string_type(inner_ty) {
            quote! {
                let #set_var: bool = std::env::var(#env_lit).is_ok();
                let #var_name: Option<String> = std::env::var(#env_lit).ok();
            }
        } else if is_bool_type(inner_ty) {
            quote! {
                let #set_var: bool = std::env::var(#env_lit).is_ok();
                let #var_name: Option<bool> = match std::env::var(#env_lit) {
                    Ok(__v) => match __v.as_str() {
                        "true" | "1" => Some(true),
                        "false" | "0" => Some(false),
                        _ => {
                            __errors.push(format!("{}='{}' is not a valid bool", #env_lit, __v));
                            None
                        }
                    },
                    Err(_) => None,
                };
            }
        } else {
            quote! {
                let #set_var: bool = std::env::var(#env_lit).is_ok();
                let #var_name: Option<#inner_ty> = match std::env::var(#env_lit) {
                    Ok(__v) => match __v.parse::<#inner_ty>() {
                        Ok(__parsed) => Some(__parsed),
                        Err(_) => {
                            __errors.push(format!("{}='{}' is not a valid {}", #env_lit, __v, stringify!(#inner_ty)));
                            None
                        }
                    },
                    Err(_) => None,
                };
            }
        }
    } else if is_string_type(&field.ty) {
        quote! {
            let #var_name: Option<String> = std::env::var(#env_lit).ok();
        }
    } else if is_bool_type(&field.ty) {
        quote! {
            let #var_name: Option<bool> = match std::env::var(#env_lit) {
                Ok(__v) => match __v.as_str() {
                    "true" | "1" => Some(true),
                    "false" | "0" => Some(false),
                    _ => {
                        __errors.push(format!("{}='{}' is not a valid bool", #env_lit, __v));
                        None
                    }
                },
                Err(_) => None,
            };
        }
    } else {
        let ty = &field.ty;
        quote! {
            let #var_name: Option<#ty> = match std::env::var(#env_lit) {
                Ok(__v) => match __v.parse::<#ty>() {
                    Ok(__parsed) => Some(__parsed),
                    Err(_) => {
                        __errors.push(format!("{}='{}' is not a valid {}", #env_lit, __v, stringify!(#ty)));
                        None
                    }
                },
                Err(_) => None,
            };
        }
    }
}

fn default_expr_for(field: &UnifiedField, ty: &Type) -> TokenStream {
    field.default_value.as_ref().map(|d| {
        if is_string_type(ty) {
            quote! { (#d).into() }
        } else {
            quote! { #d }
        }
    }).unwrap_or_else(|| {
        if is_string_type(ty) {
            quote! { String::new() }
        } else if is_bool_type(ty) {
            quote! { false }
        } else {
            quote! { 0 as #ty }
        }
    })
}

fn gen_merge_binding(field: &UnifiedField) -> TokenStream {
    let name = &field.name;
    let ty = &field.ty;
    let env_var = format_ident!("__env_{}", name);
    let is_option = extract_option_inner(ty).is_some();

    match field.kind {
        FieldKind::Required => {
            if is_option {
                let set_var = format_ident!("__env_{}_set", name);
                quote! { let #name: #ty = if #set_var { #env_var } else { None }; }
            } else if is_string_type(ty) {
                quote! { let #name: #ty = #env_var.unwrap_or_default(); }
            } else if is_bool_type(ty) {
                quote! { let #name: #ty = #env_var.unwrap_or(false); }
            } else {
                quote! { let #name: #ty = #env_var.unwrap_or(0 as #ty); }
            }
        }
        FieldKind::Readonly => {
            if is_option {
                let set_var = format_ident!("__env_{}_set", name);
                quote! { let #name: #ty = if #set_var { #env_var } else { None }; }
            } else {
                let default_expr = default_expr_for(field, ty);
                quote! { let #name: #ty = #env_var.unwrap_or(#default_expr); }
            }
        }
        FieldKind::Mutable => {
            if is_option {
                let set_var = format_ident!("__env_{}_set", name);
                quote! {
                    let #name: #ty = if #set_var {
                        #env_var
                    } else {
                        __json.#name.clone()
                    };
                }
            } else {
                let default_expr = default_expr_for(field, ty);
                quote! {
                    let #name: #ty = #env_var
                        .or(__json.#name.clone())
                        .unwrap_or(#default_expr);
                }
            }
        }
    }
}