use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Lit, Meta, Type};

pub fn impl_table(input: &DeriveInput) -> syn::Result<TokenStream> {
    let struct_name = &input.ident;

    let TableAttrs {
        name: table_name,
        soft_delete,
        auth_required,
        permission_area,
        owner_field,
    } = extract_table_attrs(input)?;

    // Extract hooks from #[hooks(before_create = "...", after_create = "...", ...)]
    let hooks = extract_hooks_attr(input)?;

    // Get named fields
    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(named) => &named.named,
            _ => {
                return Err(syn::Error::new_spanned(
                    input,
                    "Table derive requires named fields",
                ))
            }
        },
        _ => {
            return Err(syn::Error::new_spanned(
                input,
                "Table derive only works on structs",
            ))
        }
    };

    // Build column and index definitions
    let mut column_tokens = Vec::new();
    let mut index_tokens = Vec::new();

    for field in fields {
        let field_name = field
            .ident
            .as_ref()
            .ok_or_else(|| syn::Error::new_spanned(field, "Expected named field"))?;
        let field_name_str = field_name.to_string();

        // Check for #[key], #[timestamp], #[default("...")], #[references("table.col")], #[on_delete("cascade")], #[unique], #[index]
        let is_key = has_attr(&field.attrs, "key");
        let is_timestamp = has_attr(&field.attrs, "timestamp");
        let default_value = extract_default_value(&field.attrs);
        let references = extract_string_attr(&field.attrs, "references");
        let on_delete = extract_string_attr(&field.attrs, "on_delete");
        let is_unique = has_attr(&field.attrs, "unique");
        let has_index = has_attr(&field.attrs, "index");
        let validations = extract_validations(&field.attrs);

        // Determine type and nullable
        let (inner_type, nullable) = unwrap_option(&field.ty);
        let column_type = rust_type_to_column_type(inner_type)?;

        let auto_generate = is_key || is_timestamp;
        let primary_key = is_key;

        let default_token = match &default_value {
            Some(val) => quote! { ::std::option::Option::Some(#val.to_string()) },
            None => quote! { ::std::option::Option::None },
        };

        let references_token = match &references {
            Some(val) => quote! { ::std::option::Option::Some(#val.to_string()) },
            None => quote! { ::std::option::Option::None },
        };

        let on_delete_token = match on_delete.as_deref() {
            Some("cascade") => {
                quote! { ::std::option::Option::Some(::cufflink::manifest::ForeignKeyAction::Cascade) }
            }
            Some("set_null") => {
                quote! { ::std::option::Option::Some(::cufflink::manifest::ForeignKeyAction::SetNull) }
            }
            Some("restrict") => {
                quote! { ::std::option::Option::Some(::cufflink::manifest::ForeignKeyAction::Restrict) }
            }
            Some("no_action") => {
                quote! { ::std::option::Option::Some(::cufflink::manifest::ForeignKeyAction::NoAction) }
            }
            _ => quote! { ::std::option::Option::None },
        };

        let validation_tokens: Vec<_> = validations.iter().map(|v| {
            match v.as_str() {
                s if s.starts_with("regex=") => {
                    let pattern = &s[6..];
                    quote! { ::cufflink::manifest::ValidationRule::Regex { pattern: #pattern.to_string() } }
                }
                s if s.starts_with("min=") => {
                    let val: f64 = s[4..].parse().unwrap_or(0.0);
                    quote! { ::cufflink::manifest::ValidationRule::Min { value: #val } }
                }
                s if s.starts_with("max=") => {
                    let val: f64 = s[4..].parse().unwrap_or(0.0);
                    quote! { ::cufflink::manifest::ValidationRule::Max { value: #val } }
                }
                s if s.starts_with("min_length=") => {
                    let val: usize = s[11..].parse().unwrap_or(0);
                    quote! { ::cufflink::manifest::ValidationRule::MinLength { value: #val } }
                }
                s if s.starts_with("max_length=") => {
                    let val: usize = s[11..].parse().unwrap_or(0);
                    quote! { ::cufflink::manifest::ValidationRule::MaxLength { value: #val } }
                }
                _ => quote! {}
            }
        }).collect();

        column_tokens.push(quote! {
            ::cufflink::manifest::ColumnDefinition {
                name: #field_name_str.to_string(),
                column_type: #column_type,
                primary_key: #primary_key,
                nullable: #nullable,
                auto_generate: #auto_generate,
                default_value: #default_token,
                references: #references_token,
                on_delete: #on_delete_token,
                unique: #is_unique,
                validations: vec![#(#validation_tokens),*],
            }
        });

        // If #[index] attribute, generate an index definition for this column
        if has_index {
            let idx_name = format!("idx_{}_{}", table_name, field_name_str);
            index_tokens.push(quote! {
                ::cufflink::manifest::IndexDefinition {
                    name: #idx_name.to_string(),
                    columns: vec![#field_name_str.to_string()],
                    unique: false,
                }
            });
        }
    }

    let permission_area_token = opt_str_token(&permission_area);
    let owner_field_token = opt_str_token(&owner_field);

    let hooks_token = match &hooks {
        Some(h) => {
            let bc = opt_str_token(&h.before_create);
            let ac = opt_str_token(&h.after_create);
            let bu = opt_str_token(&h.before_update);
            let au = opt_str_token(&h.after_update);
            let bd = opt_str_token(&h.before_delete);
            let ad = opt_str_token(&h.after_delete);
            quote! {
                ::std::option::Option::Some(::cufflink::manifest::TableHooks {
                    before_create: #bc,
                    after_create: #ac,
                    before_update: #bu,
                    after_update: #au,
                    before_delete: #bd,
                    after_delete: #ad,
                })
            }
        }
        None => quote! { ::std::option::Option::None },
    };

    let expanded = quote! {
        impl #struct_name {
            #[doc(hidden)]
            pub fn __cufflink_table_def() -> ::cufflink::manifest::TableDefinition {
                ::cufflink::manifest::TableDefinition {
                    name: #table_name.to_string(),
                    columns: vec![#(#column_tokens),*],
                    indexes: vec![#(#index_tokens),*],
                    soft_delete: #soft_delete,
                    owner_field: #owner_field_token,
                    auth_required: #auth_required,
                    permission_area: #permission_area_token,
                    hooks: #hooks_token,
                }
            }
        }
    };

    Ok(expanded)
}

struct TableAttrs {
    name: String,
    soft_delete: bool,
    auth_required: bool,
    permission_area: Option<String>,
    owner_field: Option<String>,
}

fn meta_str(nv: &syn::MetaNameValue) -> Option<String> {
    if let syn::Expr::Lit(expr_lit) = &nv.value {
        if let Lit::Str(lit_str) = &expr_lit.lit {
            return Some(lit_str.value());
        }
    }
    None
}

fn extract_table_attrs(input: &DeriveInput) -> syn::Result<TableAttrs> {
    let mut name = None;
    let mut soft_delete = false;
    let mut auth_required = false;
    let mut permission_area = None;
    let mut owner_field = None;

    for attr in &input.attrs {
        if !attr.path().is_ident("table") {
            continue;
        }
        let nested = attr.parse_args_with(
            syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated,
        )?;
        for meta in nested {
            match &meta {
                Meta::NameValue(nv) if nv.path.is_ident("name") => name = meta_str(nv),
                Meta::NameValue(nv) if nv.path.is_ident("permission_area") => {
                    permission_area = meta_str(nv)
                }
                Meta::NameValue(nv) if nv.path.is_ident("owner_field") => {
                    owner_field = meta_str(nv)
                }
                Meta::Path(p) if p.is_ident("soft_delete") => soft_delete = true,
                Meta::Path(p) if p.is_ident("auth_required") => auth_required = true,
                _ => {}
            }
        }
    }

    let name = name.ok_or_else(|| {
        syn::Error::new_spanned(input, r#"#[table(name = "...")] attribute is required"#)
    })?;

    Ok(TableAttrs {
        name,
        soft_delete,
        auth_required,
        permission_area,
        owner_field,
    })
}

/// Parsed hooks from #[hooks(before_create = "...", after_create = "...", ...)]
struct ParsedHooks {
    before_create: Option<String>,
    after_create: Option<String>,
    before_update: Option<String>,
    after_update: Option<String>,
    before_delete: Option<String>,
    after_delete: Option<String>,
}

fn extract_hooks_attr(input: &DeriveInput) -> syn::Result<Option<ParsedHooks>> {
    for attr in &input.attrs {
        if attr.path().is_ident("hooks") {
            let nested = attr.parse_args_with(
                syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated,
            )?;

            let mut hooks = ParsedHooks {
                before_create: None,
                after_create: None,
                before_update: None,
                after_update: None,
                before_delete: None,
                after_delete: None,
            };

            for meta in nested {
                if let Meta::NameValue(nv) = &meta {
                    if let syn::Expr::Lit(expr_lit) = &nv.value {
                        if let Lit::Str(lit_str) = &expr_lit.lit {
                            let val = lit_str.value();
                            if nv.path.is_ident("before_create") {
                                hooks.before_create = Some(val);
                            } else if nv.path.is_ident("after_create") {
                                hooks.after_create = Some(val);
                            } else if nv.path.is_ident("before_update") {
                                hooks.before_update = Some(val);
                            } else if nv.path.is_ident("after_update") {
                                hooks.after_update = Some(val);
                            } else if nv.path.is_ident("before_delete") {
                                hooks.before_delete = Some(val);
                            } else if nv.path.is_ident("after_delete") {
                                hooks.after_delete = Some(val);
                            }
                        }
                    }
                }
            }

            return Ok(Some(hooks));
        }
    }
    Ok(None)
}

fn opt_str_token(val: &Option<String>) -> TokenStream {
    match val {
        Some(s) => quote! { ::std::option::Option::Some(#s.to_string()) },
        None => quote! { ::std::option::Option::None },
    }
}

fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool {
    attrs.iter().any(|a| a.path().is_ident(name))
}

fn extract_default_value(attrs: &[syn::Attribute]) -> Option<String> {
    extract_string_attr(attrs, "default")
}

fn extract_string_attr(attrs: &[syn::Attribute], name: &str) -> Option<String> {
    for attr in attrs {
        if attr.path().is_ident(name) {
            if let Ok(Lit::Str(s)) = attr.parse_args::<Lit>() {
                return Some(s.value());
            }
        }
    }
    None
}

/// Extract validation rules from #[validate("regex=...", "min=...", ...)]
fn extract_validations(attrs: &[syn::Attribute]) -> Vec<String> {
    let mut rules = Vec::new();
    for attr in attrs {
        if attr.path().is_ident("validate") {
            if let Ok(nested) = attr.parse_args_with(
                syn::punctuated::Punctuated::<Lit, syn::Token![,]>::parse_terminated,
            ) {
                for lit in nested {
                    if let Lit::Str(s) = lit {
                        rules.push(s.value());
                    }
                }
            }
        }
    }
    rules
}

/// If the type is Option<T>, return (T, true). Otherwise return (ty, false).
fn unwrap_option(ty: &Type) -> (&Type, bool) {
    if let Type::Path(type_path) = ty {
        if let Some(seg) = type_path.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 (inner, true);
                    }
                }
            }
        }
    }
    (ty, false)
}

/// Map Rust types to ColumnType token expressions
fn rust_type_to_column_type(ty: &Type) -> syn::Result<TokenStream> {
    let type_name = extract_type_name(ty);

    let column_type = match type_name.as_str() {
        "Uuid" => quote! { ::cufflink::manifest::ColumnType::Uuid },
        "String" => quote! { ::cufflink::manifest::ColumnType::Text },
        "bool" => quote! { ::cufflink::manifest::ColumnType::Boolean },
        "i32" => quote! { ::cufflink::manifest::ColumnType::Integer },
        "i64" => quote! { ::cufflink::manifest::ColumnType::BigInteger },
        "f32" => quote! { ::cufflink::manifest::ColumnType::Float },
        "f64" => quote! { ::cufflink::manifest::ColumnType::Double },
        "DateTime" => quote! { ::cufflink::manifest::ColumnType::Timestamp },
        "NaiveDate" => quote! { ::cufflink::manifest::ColumnType::Date },
        "Value" => quote! { ::cufflink::manifest::ColumnType::Jsonb },
        other => {
            return Err(syn::Error::new_spanned(
                ty,
                format!("Unsupported type '{}'. Supported: Uuid, String, bool, i32, i64, f32, f64, DateTime<Utc>, NaiveDate, Value", other),
            ));
        }
    };

    Ok(column_type)
}

/// Extract the last segment name from a type path (e.g. "DateTime" from "chrono::DateTime<Utc>")
fn extract_type_name(ty: &Type) -> String {
    match ty {
        Type::Path(type_path) => {
            if let Some(seg) = type_path.path.segments.last() {
                seg.ident.to_string()
            } else {
                String::new()
            }
        }
        _ => String::new(),
    }
}
