use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitInt, LitStr, Token};

struct JobOpts {
    timeout_secs: u32,
    max_attempts: u32,
}

struct RouteEntry {
    method: LitStr,
    path: LitStr,
    handler: LitStr,
    job: Option<JobOpts>,
}

struct SubscriptionEntry {
    subject: LitStr,
    handler: LitStr,
    handler_type: Ident,
    stream: Option<LitStr>,
}

struct AreaEntry {
    name: LitStr,
    operations: Vec<LitStr>,
}

struct DefaultRoleEntry {
    name: LitStr,
    description: LitStr,
    permissions: Vec<LitStr>,
}

struct AuthorizationBlock {
    areas: Vec<AreaEntry>,
    default_roles: Vec<DefaultRoleEntry>,
}

/// Parsed contents of service! { name: "...", tables: [...], mode: wasm, custom_routes: [...], subscriptions: [...], authorization: [...], on_migrate: "..." }
struct ServiceDef {
    name: LitStr,
    tables: Vec<Ident>,
    mode: Option<Ident>,
    custom_routes: Vec<RouteEntry>,
    subscriptions: Vec<SubscriptionEntry>,
    authorization: Option<AuthorizationBlock>,
    on_migrate: Option<LitStr>,
}

impl Parse for ServiceDef {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut name: Option<LitStr> = None;
        let mut tables: Vec<Ident> = Vec::new();
        let mut mode: Option<Ident> = None;
        let mut custom_routes: Vec<RouteEntry> = Vec::new();
        let mut subscriptions: Vec<SubscriptionEntry> = Vec::new();
        let mut authorization: Option<AuthorizationBlock> = None;
        let mut on_migrate: Option<LitStr> = None;

        while !input.is_empty() {
            let key: Ident = input.parse()?;
            input.parse::<Token![:]>()?;

            match key.to_string().as_str() {
                "name" => {
                    name = Some(input.parse()?);
                }
                "tables" => {
                    let content;
                    syn::bracketed!(content in input);
                    tables = content
                        .parse_terminated(Ident::parse, Token![,])?
                        .into_iter()
                        .collect();
                }
                "mode" => {
                    mode = Some(input.parse()?);
                }
                "custom_routes" => {
                    let content;
                    syn::bracketed!(content in input);
                    while !content.is_empty() {
                        let tuple;
                        syn::parenthesized!(tuple in content);
                        let method: LitStr = tuple.parse()?;
                        tuple.parse::<Token![,]>()?;
                        let path: LitStr = tuple.parse()?;
                        tuple.parse::<Token![,]>()?;
                        let handler: LitStr = tuple.parse()?;
                        let job = if tuple.peek(Token![,]) {
                            tuple.parse::<Token![,]>()?;
                            Some(parse_job_opts(&tuple)?)
                        } else {
                            None
                        };
                        custom_routes.push(RouteEntry {
                            method,
                            path,
                            handler,
                            job,
                        });
                        if content.peek(Token![,]) {
                            content.parse::<Token![,]>()?;
                        }
                    }
                }
                "subscriptions" => {
                    let content;
                    syn::bracketed!(content in input);
                    while !content.is_empty() {
                        let tuple;
                        syn::parenthesized!(tuple in content);
                        let subject: LitStr = tuple.parse()?;
                        tuple.parse::<Token![,]>()?;
                        let handler: LitStr = tuple.parse()?;
                        tuple.parse::<Token![,]>()?;
                        let handler_type: Ident = tuple.parse()?;
                        let stream = if tuple.peek(Token![,]) {
                            tuple.parse::<Token![,]>()?;
                            Some(tuple.parse::<LitStr>()?)
                        } else {
                            None
                        };
                        subscriptions.push(SubscriptionEntry {
                            subject,
                            handler,
                            handler_type,
                            stream,
                        });
                        if content.peek(Token![,]) {
                            content.parse::<Token![,]>()?;
                        }
                    }
                }
                "authorization" => {
                    let content;
                    syn::bracketed!(content in input);
                    let mut areas: Vec<AreaEntry> = Vec::new();
                    let mut default_roles: Vec<DefaultRoleEntry> = Vec::new();

                    while !content.is_empty() {
                        let inner_key: Ident = content.parse()?;
                        content.parse::<Token![:]>()?;

                        match inner_key.to_string().as_str() {
                            "areas" => {
                                let areas_content;
                                syn::bracketed!(areas_content in content);
                                while !areas_content.is_empty() {
                                    let tuple;
                                    syn::parenthesized!(tuple in areas_content);
                                    let area_name: LitStr = tuple.parse()?;
                                    tuple.parse::<Token![,]>()?;
                                    let ops_content;
                                    syn::bracketed!(ops_content in tuple);
                                    let ops = ops_content
                                        .parse_terminated(
                                            |input| input.parse::<LitStr>(),
                                            Token![,],
                                        )?
                                        .into_iter()
                                        .collect();
                                    areas.push(AreaEntry {
                                        name: area_name,
                                        operations: ops,
                                    });
                                    if areas_content.peek(Token![,]) {
                                        areas_content.parse::<Token![,]>()?;
                                    }
                                }
                            }
                            "default_roles" => {
                                let roles_content;
                                syn::bracketed!(roles_content in content);
                                while !roles_content.is_empty() {
                                    let tuple;
                                    syn::parenthesized!(tuple in roles_content);
                                    let role_name: LitStr = tuple.parse()?;
                                    tuple.parse::<Token![,]>()?;
                                    let description: LitStr = tuple.parse()?;
                                    tuple.parse::<Token![,]>()?;
                                    let perms_content;
                                    syn::bracketed!(perms_content in tuple);
                                    let permissions = perms_content
                                        .parse_terminated(
                                            |input| input.parse::<LitStr>(),
                                            Token![,],
                                        )?
                                        .into_iter()
                                        .collect();
                                    default_roles.push(DefaultRoleEntry {
                                        name: role_name,
                                        description,
                                        permissions,
                                    });
                                    if roles_content.peek(Token![,]) {
                                        roles_content.parse::<Token![,]>()?;
                                    }
                                }
                            }
                            other => {
                                return Err(syn::Error::new(
                                    inner_key.span(),
                                    format!("Unknown authorization field: '{}'", other),
                                ));
                            }
                        }

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

                    authorization = Some(AuthorizationBlock {
                        areas,
                        default_roles,
                    });
                }
                "on_migrate" => {
                    on_migrate = Some(input.parse()?);
                }
                other => {
                    return Err(syn::Error::new(
                        key.span(),
                        format!("Unknown service field: '{}'", other),
                    ));
                }
            }

            // Consume optional trailing comma
            if input.peek(Token![,]) {
                input.parse::<Token![,]>()?;
            }
        }

        let name = name.ok_or_else(|| input.error("service! requires a 'name' field"))?;

        Ok(ServiceDef {
            name,
            tables,
            mode,
            custom_routes,
            subscriptions,
            authorization,
            on_migrate,
        })
    }
}

pub fn impl_service(input: TokenStream) -> syn::Result<TokenStream> {
    let def: ServiceDef = syn::parse2(input)?;

    let service_name = &def.name;
    let table_calls: Vec<_> = def
        .tables
        .iter()
        .map(|t| {
            quote! { #t::__cufflink_table_def() }
        })
        .collect();

    let mode_token = match def.mode.as_ref().map(|m| m.to_string()).as_deref() {
        Some("wasm") => quote! { ::cufflink::manifest::ServiceMode::Wasm },
        Some("container") => quote! { ::cufflink::manifest::ServiceMode::Container },
        _ => quote! { ::cufflink::manifest::ServiceMode::Crud },
    };

    let route_tokens: Vec<_> = def
        .custom_routes
        .iter()
        .map(|r| {
            let method = &r.method;
            let path = &r.path;
            let handler = &r.handler;
            let job_token = match &r.job {
                Some(j) => {
                    let timeout = j.timeout_secs;
                    let attempts = j.max_attempts;
                    quote! {
                        ::std::option::Option::Some(::cufflink::manifest::JobConfig {
                            timeout_secs: #timeout,
                            max_attempts: #attempts,
                        })
                    }
                }
                None => quote! { ::std::option::Option::None },
            };
            quote! {
                ::cufflink::manifest::CustomRouteDefinition {
                    method: #method.to_string(),
                    path: #path.to_string(),
                    handler: #handler.to_string(),
                    job: #job_token,
                }
            }
        })
        .collect();

    let subscription_tokens: Vec<_> = def
        .subscriptions
        .iter()
        .map(|s| {
            let subject = &s.subject;
            let handler = &s.handler;
            let handler_type = match s.handler_type.to_string().as_str() {
                "wasm" => quote! { ::cufflink::manifest::HandlerType::Wasm },
                "webhook" => quote! { ::cufflink::manifest::HandlerType::Webhook },
                "delete_cascade" => quote! { ::cufflink::manifest::HandlerType::DeleteCascade },
                "update_field" => quote! { ::cufflink::manifest::HandlerType::UpdateField },
                _ => quote! { ::cufflink::manifest::HandlerType::Wasm },
            };
            let stream_token = match &s.stream {
                Some(s) => quote! { ::std::option::Option::Some(#s.to_string()) },
                None => quote! { ::std::option::Option::None },
            };
            quote! {
                ::cufflink::manifest::SubscriptionDefinition {
                    subject: #subject.to_string(),
                    handler: #handler.to_string(),
                    handler_type: #handler_type,
                    config: ::std::collections::HashMap::new(),
                    stream: #stream_token,
                }
            }
        })
        .collect();

    let authorization_token = match &def.authorization {
        Some(auth) => {
            let area_tokens: Vec<_> = auth
                .areas
                .iter()
                .map(|a| {
                    let name = &a.name;
                    let ops: Vec<_> = a
                        .operations
                        .iter()
                        .map(|o| {
                            quote! { #o.to_string() }
                        })
                        .collect();
                    quote! {
                        ::cufflink::manifest::PermissionAreaDef {
                            name: #name.to_string(),
                            operations: vec![#(#ops),*],
                        }
                    }
                })
                .collect();

            let role_tokens: Vec<_> = auth
                .default_roles
                .iter()
                .map(|r| {
                    let name = &r.name;
                    let desc = &r.description;
                    let perms: Vec<_> = r
                        .permissions
                        .iter()
                        .map(|p| {
                            quote! { #p.to_string() }
                        })
                        .collect();
                    quote! {
                        ::cufflink::manifest::DefaultRoleDef {
                            name: #name.to_string(),
                            description: ::std::option::Option::Some(#desc.to_string()),
                            permissions: vec![#(#perms),*],
                        }
                    }
                })
                .collect();

            quote! {
                ::std::option::Option::Some(::cufflink::manifest::AuthorizationConfig {
                    areas: vec![#(#area_tokens),*],
                    default_roles: vec![#(#role_tokens),*],
                })
            }
        }
        None => quote! { ::std::option::Option::None },
    };

    let on_migrate_token = match &def.on_migrate {
        Some(name) => quote! { ::std::option::Option::Some(#name.to_string()) },
        None => quote! { ::std::option::Option::None },
    };

    let expanded = quote! {
        fn main() {
            let args: ::std::vec::Vec<::std::string::String> = ::std::env::args().collect();
            if args.iter().any(|a| a == "--emit-manifest") {
                let manifest = ::cufflink::manifest::ServiceManifest {
                    name: #service_name.to_string(),
                    version: ::std::option::Option::Some(env!("CARGO_PKG_VERSION").to_string()),
                    tables: vec![#(#table_calls),*],
                    cells: vec![],
                    events: vec![],
                    subscriptions: vec![#(#subscription_tokens),*],
                    custom_routes: vec![#(#route_tokens),*],
                    mode: #mode_token,
                    authorization: #authorization_token,
                    on_migrate: #on_migrate_token,
                };
                let json = ::serde_json::to_string_pretty(&manifest).unwrap();
                println!("{}", json);
                return;
            }
            println!("Cufflink service '{}' — use `cufflink deploy` to deploy.", #service_name);
        }
    };

    Ok(expanded)
}

/// Parse `job(timeout = N, max_attempts = M)`. Both fields are optional and
/// default to `JobConfig::DEFAULT_*`. A bare `job` keyword (no parens) yields
/// all defaults.
fn parse_job_opts(input: ParseStream) -> syn::Result<JobOpts> {
    let kw: Ident = input.parse()?;
    if kw != "job" {
        return Err(syn::Error::new(
            kw.span(),
            "expected `job(...)` as the 4th element of a custom_route tuple",
        ));
    }

    let mut timeout_secs: u32 = 600;
    let mut max_attempts: u32 = 3;

    if input.peek(syn::token::Paren) {
        let inner;
        syn::parenthesized!(inner in input);
        while !inner.is_empty() {
            let key: Ident = inner.parse()?;
            inner.parse::<Token![=]>()?;
            let value: LitInt = inner.parse()?;
            match key.to_string().as_str() {
                "timeout" | "timeout_secs" => {
                    timeout_secs = value.base10_parse()?;
                }
                "max_attempts" => {
                    max_attempts = value.base10_parse()?;
                }
                other => {
                    return Err(syn::Error::new(
                        key.span(),
                        format!(
                            "unknown job option '{}'; expected 'timeout' or 'max_attempts'",
                            other
                        ),
                    ));
                }
            }
            if inner.peek(Token![,]) {
                inner.parse::<Token![,]>()?;
            }
        }
    }

    if timeout_secs == 0 {
        return Err(input.error("job timeout must be > 0"));
    }
    if max_attempts == 0 {
        return Err(input.error("job max_attempts must be >= 1"));
    }

    Ok(JobOpts {
        timeout_secs,
        max_attempts,
    })
}
