use syn::{
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
    Ident, LitStr, Token, Type, Result,
};

/// HTTP method
#[derive(Debug, Clone)]
pub enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
    Patch,
    Any,
}

/// Top-level bind definition (new syntax):
///
/// ```ignore
/// bind_http! {
///     Name, "/prefix" {
///         GET "/path" => handler_fn -> RetType,
///         POST "/path" => handler_fn,
///     }
/// }
/// ```
pub struct BindDef {
    pub name: Ident,
    pub prefix: String,
    pub state_type: Type,
    pub bindings: Vec<BindingDef>,
}

/// Auth level for a route binding
#[derive(Debug, Clone, PartialEq)]
pub enum AuthLevel {
    /// Requires session authentication (default if not specified)
    Session,
    /// No authentication required (public endpoint)
    None,
}

/// A single route binding: `#[auth(level)] METHOD "path" => handler : TxType @ DB -> RetType,`
pub struct BindingDef {
    pub auth: Option<AuthLevel>,
    pub tx_type: Type,
    pub db_binding: Option<syn::Path>,
    pub method: HttpMethod,
    pub path: String,
    pub absolute: bool,
    pub handler: syn::Path,
    #[allow(dead_code)]
    pub return_type: Option<Type>,
}

impl Parse for BindDef {
    fn parse(input: ParseStream) -> Result<Self> {
        // Parse struct name
        let name: Ident = input.parse()?;

        // Parse comma
        input.parse::<Token![,]>()?;

        // Parse prefix string
        let prefix_lit: LitStr = input.parse()?;
        let prefix = prefix_lit.value();

        // Parse comma + state type
        input.parse::<Token![,]>()?;
        let state_type: Type = input.parse()?;

        // Parse `{ ... }`
        let content;
        syn::braced!(content in input);

        // Parse route bindings (no config — config goes in bind_server!)
        let mut bindings = Vec::new();
        while !content.is_empty() {
            bindings.push(content.parse::<BindingDef>()?);
        }

        Ok(BindDef {
            name,
            prefix,
            state_type,
            bindings,
        })
    }
}

impl Parse for BindingDef {
    fn parse(input: ParseStream) -> Result<Self> {
        // Parse optional #[auth(...)] attribute
        let auth = if input.peek(Token![#]) {
            let attrs = input.call(syn::Attribute::parse_outer)?;
            let mut auth_level = None;
            for attr in &attrs {
                if attr.path().is_ident("auth") {
                    let level: Ident = attr.parse_args()?;
                    auth_level = Some(match level.to_string().as_str() {
                        "session" => AuthLevel::Session,
                        "none" => AuthLevel::None,
                        other => {
                            return Err(syn::Error::new(
                                level.span(),
                                format!(
                                    "unknown auth level: `{}`, expected `session` or `none`",
                                    other
                                ),
                            ));
                        }
                    });
                }
            }
            auth_level
        } else {
            None
        };

        // Parse HTTP method
        let method_ident: Ident = input.parse()?;
        let method = parse_http_method(&method_ident)?;

        // Parse path string (! prefix = absolute path, skips base prefix)
        let path_lit: LitStr = input.parse()?;
        let raw_path = path_lit.value();
        let (path, absolute) = if let Some(stripped) = raw_path.strip_prefix('!') {
            (stripped.to_string(), true)
        } else {
            (raw_path, false)
        };

        // Parse `=>`
        input.parse::<Token![=>]>()?;

        // Parse handler function path (supports module::handler)
        let handler: syn::Path = input.parse()?;

        // Parse required `: TxType` (transaction fatal type)
        input.parse::<Token![:]>()?;
        let tx_type: Type = input.parse()?;

        // Parse optional `@ DB_BINDING` (database binding)
        let db_binding = if input.peek(Token![@]) {
            input.parse::<Token![@]>()?;
            Some(input.parse::<syn::Path>()?)
        } else {
            None
        };

        // Parse optional return type: `-> Type`
        let return_type = if input.peek(Token![->]) {
            input.parse::<Token![->]>()?;
            Some(input.parse::<Type>()?)
        } else {
            None
        };

        // Parse trailing comma (optional for last item)
        let _ = input.parse::<Token![,]>();

        Ok(BindingDef {
            auth,
            tx_type,
            db_binding,
            method,
            path,
            absolute,
            handler,
            return_type,
        })
    }
}

fn parse_http_method(ident: &Ident) -> Result<HttpMethod> {
    match ident.to_string().as_str() {
        "GET" => Ok(HttpMethod::Get),
        "POST" => Ok(HttpMethod::Post),
        "PUT" => Ok(HttpMethod::Put),
        "DELETE" => Ok(HttpMethod::Delete),
        "PATCH" => Ok(HttpMethod::Patch),
        "ANY" => Ok(HttpMethod::Any),
        other => Err(syn::Error::new(
            ident.span(),
            format!("unknown HTTP method: {}", other),
        )),
    }
}

// ===========================================================================
// bind_server! parsing
// ===========================================================================

/// Server-level configuration definition:
///
/// ```ignore
/// bind_server! {
///     ServerName, StateType {
///         auth_policy: PolicyType,
///         session_cookie_prefix: "prefix_",
///         public_dir: "apps/",
///         auth_paths: { login: "/login", root: "/" },
///         embedded_pages: { "/" => "index.html" },
///         body_limit: 200 * BYTES_PER_MB,
///     }
/// }
/// ```
pub struct ServerDef {
    pub name: Ident,
    pub state_type: Type,
    pub session_cookie_prefix: Option<String>,
    pub public_dir: Option<String>,
    pub auth_paths: Vec<(String, String)>,
    pub embedded_pages: Vec<(String, String)>,
    pub body_limit: Option<syn::Expr>,
    pub auth_policy: Option<Type>,
}

impl Parse for ServerDef {
    fn parse(input: ParseStream) -> Result<Self> {
        let name: Ident = input.parse()?;
        input.parse::<Token![,]>()?;
        let state_type: Type = input.parse()?;

        let content;
        syn::braced!(content in input);

        let mut session_cookie_prefix = None;
        let mut public_dir = None;
        let mut auth_paths = Vec::new();
        let mut embedded_pages = Vec::new();
        let mut body_limit = None;
        let mut auth_policy = None;

        while !content.is_empty() {
            if !content.peek(Ident) {
                break;
            }
            let fork = content.fork();
            let ident: Ident = fork.parse()?;
            let ident_str = ident.to_string();

            match ident_str.as_str() {
                "session_cookie_prefix" => {
                    let _: Ident = content.parse()?;
                    content.parse::<Token![:]>()?;
                    let lit: LitStr = content.parse()?;
                    session_cookie_prefix = Some(lit.value());
                    let _ = content.parse::<Token![,]>();
                }
                "public_dir" => {
                    let _: Ident = content.parse()?;
                    content.parse::<Token![:]>()?;
                    let lit: LitStr = content.parse()?;
                    public_dir = Some(lit.value());
                    let _ = content.parse::<Token![,]>();
                }
                "auth_paths" => {
                    let _: Ident = content.parse()?;
                    content.parse::<Token![:]>()?;
                    let paths_content;
                    syn::braced!(paths_content in content);
                    while !paths_content.is_empty() {
                        let key: Ident = paths_content.parse()?;
                        paths_content.parse::<Token![:]>()?;
                        let val: LitStr = paths_content.parse()?;
                        auth_paths.push((key.to_string(), val.value()));
                        let _ = paths_content.parse::<Token![,]>();
                    }
                    let _ = content.parse::<Token![,]>();
                }
                "embedded_pages" => {
                    let _: Ident = content.parse()?;
                    content.parse::<Token![:]>()?;
                    let pages_content;
                    syn::braced!(pages_content in content);
                    while !pages_content.is_empty() {
                        let url_path: LitStr = pages_content.parse()?;
                        pages_content.parse::<Token![=>]>()?;
                        let file_path: LitStr = pages_content.parse()?;
                        embedded_pages.push((url_path.value(), file_path.value()));
                        let _ = pages_content.parse::<Token![,]>();
                    }
                    let _ = content.parse::<Token![,]>();
                }
                "body_limit" => {
                    let _: Ident = content.parse()?;
                    content.parse::<Token![:]>()?;
                    let expr: syn::Expr = content.parse()?;
                    body_limit = Some(expr);
                    let _ = content.parse::<Token![,]>();
                }
                "auth_policy" => {
                    let _: Ident = content.parse()?;
                    content.parse::<Token![:]>()?;
                    let ty: Type = content.parse()?;
                    auth_policy = Some(ty);
                    let _ = content.parse::<Token![,]>();
                }
                other => {
                    return Err(syn::Error::new(
                        ident.span(),
                        format!("unknown server config key: `{}`", other),
                    ));
                }
            }
        }

        Ok(ServerDef {
            name,
            state_type,
            session_cookie_prefix,
            public_dir,
            auth_paths,
            embedded_pages,
            body_limit,
            auth_policy,
        })
    }
}