use crate::codegen::common::OrmliteCodegen;
use crate::codegen::insert::impl_ModelBuilder__insert;
use crate::codegen::update::impl_ModelBuilder__update;
use ormlite_attr::ModelMeta;
use ormlite_attr::TableMeta;
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

pub fn struct_ModelBuilder(ast: &DeriveInput, attr: &ModelMeta) -> TokenStream {
    let model = &attr.ident;
    let model_builder = attr.builder_struct();
    let vis = &ast.vis;

    let settable = attr.database_columns().map(|c| {
        let name = &c.ident;
        let ty = &c.ty;
        quote! { pub #name: std::option::Option<#ty> }
    });

    let methods = attr.database_columns().map(|c| {
        let name = &c.ident;
        let ty = &c.ty;
        if ty.is_string() {
            quote! {
                pub fn #name<T: Into<String>>(mut self, #name: T) -> Self {
                    self.#name = Some(#name.into());
                    self
                }
            }
        } else {
            quote! {
                pub fn #name(mut self, #name: #ty) -> Self {
                    self.#name = Some(#name);
                    self
                }
            }
        }
    });

    let fields_none = attr.database_columns().map(|c| {
        let name = &c.ident;
        quote! {
            #name: None
        }
    });

    quote! {
        #vis struct #model_builder<'a> {
            #(#settable,)*
            updating: Option<&'a #model>,
        }

        impl<'a> std::default::Default for #model_builder<'a> {
            fn default() -> Self {
                Self {
                    #(#fields_none,)*
                    updating: None,
                }
            }
        }

        impl<'a> #model_builder<'a> {
            #(#methods)*

        }
    }
}

pub fn impl_ModelBuilder__build(attr: &TableMeta) -> TokenStream {
    let unpack = attr.database_columns().map(|c| {
        let c = &c.ident;
        let msg = format!("Tried to build a model, but the field `{}` was not set.", c);
        quote! { let #c = self.#c.expect(#msg); }
    });

    let fields = attr.database_columns().map(|c| &c.ident);

    let skipped_fields = attr.columns.iter().filter(|&c| c.skip).map(|c| {
        let id = &c.ident;
        quote! {
            #id: Default::default()
        }
    });

    quote! {
        fn build(self) -> Self::Model {
            #( #unpack )*
            Self::Model {
                #( #fields, )*
                #( #skipped_fields, )*
            }
        }
    }
}

pub fn impl_ModelBuilder(db: &dyn OrmliteCodegen, attr: &ModelMeta) -> TokenStream {
    let partial_model = attr.builder_struct();
    let model = &attr.ident;

    let impl_ModelBuilder__insert = impl_ModelBuilder__insert(db, &attr.table);
    let impl_ModelBuilder__update = impl_ModelBuilder__update(db, attr);
    let impl_ModelBuilder__build = impl_ModelBuilder__build(&attr.table);

    let build_modified_fields = attr.database_columns().map(|c| {
        let name = &c.ident;
        let name_str = &c.name;
        quote! {
            if self.#name.is_some() {
                ret.push(#name_str);
            }
        }
    });

    let db = db.database_ts();
    quote! {
        impl<'a> ::ormlite::model::ModelBuilder<'a, #db> for #partial_model<'a> {
            type Model = #model;
            #impl_ModelBuilder__insert
            #impl_ModelBuilder__update
            #impl_ModelBuilder__build

            fn modified_fields(&self) -> Vec<&'static str> {
                let mut ret = Vec::new();
                #(#build_modified_fields)*
                ret
            }
        }
    }
}
