use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use serde::Deserialize;
use syn::{LitStr, parse_macro_input};

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct ToolSpec {
    name: String,
    description: Option<String>,
    input_schema: serde_json::Value,
    output_schema: Option<serde_json::Value>,
}

#[derive(Debug, Deserialize)]
struct ClientSpec {
    endpoint: String,
    methods: Vec<ToolSpec>,
}

fn to_rust_type(schema: &serde_json::Value) -> TokenStream2 {
    if let Some(types) = schema.get("type").and_then(|t| t.as_array()) {
        let has_null = types.iter().any(|t| t.as_str() == Some("null"));
        let non_null_types: Vec<_> = types
            .iter()
            .filter(|t| t.as_str() != Some("null"))
            .collect();

        if non_null_types.len() == 1 {
            let mut schema_without_null = schema.clone();
            schema_without_null["type"] = non_null_types[0].clone();
            let inner_type = to_rust_type(&schema_without_null);
            if has_null {
                return quote! { Option<#inner_type> };
            }
            return inner_type;
        }
        return quote! { serde_json::Value };
    }

    if schema.get("oneOf").is_some() || schema.get("anyOf").is_some() {
        return quote! { serde_json::Value };
    }

    if schema.get("enum").is_some() {
        return quote! { String };
    }

    let nullable = schema.get("nullable").and_then(|n| n.as_bool()) == Some(true);

    let base_type = match schema.get("type").and_then(|t| t.as_str()) {
        Some("string") => match schema.get("format").and_then(|f| f.as_str()) {
            Some("date-time") | Some("date") | Some("time") => quote! { String },
            Some("uuid") => quote! { String },
            Some("uri") | Some("uri-reference") => quote! { String },
            Some("email") => quote! { String },
            Some("byte") => quote! { String },
            _ => quote! { String },
        },
        Some("integer") => match schema.get("format").and_then(|f| f.as_str()) {
            Some("int32") => quote! { i32 },
            Some("int64") => quote! { i64 },
            Some("uint32") => quote! { u32 },
            Some("uint64") => quote! { u64 },
            _ => quote! { i64 },
        },
        Some("number") => match schema.get("format").and_then(|f| f.as_str()) {
            Some("float") => quote! { f32 },
            Some("double") => quote! { f64 },
            _ => quote! { f64 },
        },
        Some("boolean") => quote! { bool },
        Some("null") => quote! { () },
        Some("array") => {
            let default_items = serde_json::json!({});
            let items = schema.get("items").unwrap_or(&default_items);
            let item_type = to_rust_type(items);
            quote! { Vec<#item_type> }
        }
        Some("object") => {
            if let Some(additional) = schema.get("additionalProperties") {
                if additional.is_object() {
                    let value_type = to_rust_type(additional);
                    let map_type = quote! { std::collections::HashMap<String, #value_type> };
                    if nullable {
                        return quote! { Option<#map_type> };
                    }
                    return map_type;
                }
                if additional.as_bool() == Some(true) {
                    let map_type = quote! { std::collections::HashMap<String, serde_json::Value> };
                    if nullable {
                        return quote! { Option<#map_type> };
                    }
                    return map_type;
                }
            }
            quote! { serde_json::Value }
        }
        None => quote! { serde_json::Value },
        _ => quote! { serde_json::Value },
    };

    if nullable {
        quote! { Option<#base_type> }
    } else {
        base_type
    }
}

fn to_snake_case(s: &str) -> String {
    let mut result = String::new();
    for (i, c) in s.chars().enumerate() {
        if c.is_uppercase() && i > 0 {
            result.push('_');
        }
        result.push(c.to_lowercase().next().unwrap());
    }
    result.replace('-', "_")
}

fn to_pascal_case(s: &str) -> String {
    s.split(['_', '-'])
        .map(|word| {
            let mut chars = word.chars();
            match chars.next() {
                Some(first) => first.to_uppercase().chain(chars).collect(),
                None => String::new(),
            }
        })
        .collect()
}

fn generate_input_struct(tool: &ToolSpec) -> TokenStream2 {
    let struct_name = format_ident!("{}Input", to_pascal_case(&tool.name));

    if let Some(properties) = tool.input_schema.get("properties")
        && let Some(props) = properties.as_object()
    {
        let required: Vec<String> = tool
            .input_schema
            .get("required")
            .and_then(|r| r.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(String::from))
                    .collect()
            })
            .unwrap_or_default();

        let fields: Vec<TokenStream2> = props
            .iter()
            .map(|(name, schema)| {
                let snake_name = to_snake_case(name);
                let field_name = format_ident!("{}", snake_name);
                let field_type = to_rust_type(schema);
                let is_required = required.contains(name);
                let needs_rename = &snake_name != name;

                let rename_attr = if needs_rename {
                    quote! { #[serde(rename = #name)] }
                } else {
                    quote! {}
                };

                if is_required {
                    quote! {
                        #rename_attr
                        pub #field_name: #field_type
                    }
                } else {
                    quote! {
                        #rename_attr
                        #[serde(skip_serializing_if = "Option::is_none")]
                        pub #field_name: Option<#field_type>
                    }
                }
            })
            .collect();

        return quote! {
            #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
            pub struct #struct_name {
                #(#fields),*
            }
        };
    }

    quote! {
        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
        pub struct #struct_name {
            #[serde(flatten)]
            pub data: serde_json::Value,
        }
    }
}

fn generate_output_struct(tool: &ToolSpec) -> TokenStream2 {
    let struct_name = format_ident!("{}Output", to_pascal_case(&tool.name));

    if let Some(ref output_schema) = tool.output_schema
        && let Some(properties) = output_schema.get("properties")
        && let Some(props) = properties.as_object()
    {
        let required: Vec<String> = output_schema
            .get("required")
            .and_then(|r| r.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(String::from))
                    .collect()
            })
            .unwrap_or_default();

        let fields: Vec<TokenStream2> = props
            .iter()
            .map(|(name, schema)| {
                let snake_name = to_snake_case(name);
                let field_name = format_ident!("{}", snake_name);
                let field_type = to_rust_type(schema);
                let is_required = required.contains(name);
                let needs_rename = &snake_name != name;

                let rename_attr = if needs_rename {
                    quote! { #[serde(rename = #name)] }
                } else {
                    quote! {}
                };

                if is_required {
                    quote! {
                        #rename_attr
                        pub #field_name: #field_type
                    }
                } else {
                    quote! {
                        #rename_attr
                        #[serde(skip_serializing_if = "Option::is_none")]
                        pub #field_name: Option<#field_type>
                    }
                }
            })
            .collect();

        return quote! {
            #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
            pub struct #struct_name {
                #(#fields),*
            }
        };
    }

    quote! {
        #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
        pub struct #struct_name {
            #[serde(flatten)]
            pub data: serde_json::Value,
        }
    }
}

fn generate_method(tool: &ToolSpec) -> TokenStream2 {
    let method_name = format_ident!("{}", to_snake_case(&tool.name));
    let input_type = format_ident!("{}Input", to_pascal_case(&tool.name));
    let output_type = format_ident!("{}Output", to_pascal_case(&tool.name));
    let tool_name = &tool.name;

    quote! {
        pub async fn #method_name(&self, input: #input_type) -> rpc::Result<#output_type> {
            self.client.call_tool_typed(#tool_name, input).await
        }
    }
}

pub fn impl_mcp_client_from_json(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as LitStr);
    let json_str = input.value();

    let spec: ClientSpec = match serde_json::from_str(&json_str) {
        Ok(s) => s,
        Err(e) => {
            return syn::Error::new(input.span(), format!("Invalid JSON spec: {}", e))
                .to_compile_error()
                .into();
        }
    };

    let client_struct = quote! {
        pub struct GeneratedMcpClient {
            client: rpc::McpClient,
        }
    };

    let input_structs: Vec<TokenStream2> = spec.methods.iter().map(generate_input_struct).collect();
    let output_structs: Vec<TokenStream2> =
        spec.methods.iter().map(generate_output_struct).collect();
    let methods: Vec<TokenStream2> = spec.methods.iter().map(generate_method).collect();

    let endpoint = &spec.endpoint;

    let expanded = quote! {
        #(#input_structs)*

        #(#output_structs)*

        #client_struct

        impl GeneratedMcpClient {
            pub async fn connect() -> rpc::Result<Self> {
                let client = rpc::McpClient::builder()
                    .endpoint(#endpoint)
                    .build()
                    .await?;
                Ok(Self { client })
            }

            pub async fn connect_with_endpoint(endpoint: &str) -> rpc::Result<Self> {
                let client = rpc::McpClient::builder()
                    .endpoint(endpoint)
                    .build()
                    .await?;
                Ok(Self { client })
            }

            pub fn mcp_client(&self) -> &rpc::McpClient {
                &self.client
            }

            #(#methods)*
        }
    };

    TokenStream::from(expanded)
}

pub fn impl_mcp_client_from_file(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as LitStr);
    let file_path = input.value();

    let json_str = match std::fs::read_to_string(&file_path) {
        Ok(s) => s,
        Err(e) => {
            return syn::Error::new(
                input.span(),
                format!("Cannot read file '{}': {}", file_path, e),
            )
            .to_compile_error()
            .into();
        }
    };

    let spec: ClientSpec = match serde_json::from_str(&json_str) {
        Ok(s) => s,
        Err(e) => {
            return syn::Error::new(
                input.span(),
                format!("Invalid JSON in '{}': {}", file_path, e),
            )
            .to_compile_error()
            .into();
        }
    };

    let input_structs: Vec<TokenStream2> = spec.methods.iter().map(generate_input_struct).collect();
    let output_structs: Vec<TokenStream2> =
        spec.methods.iter().map(generate_output_struct).collect();
    let methods: Vec<TokenStream2> = spec.methods.iter().map(generate_method).collect();

    let endpoint = &spec.endpoint;

    let expanded = quote! {
        #(#input_structs)*

        #(#output_structs)*

        pub struct GeneratedMcpClient {
            client: rpc::McpClient,
        }

        impl GeneratedMcpClient {
            pub async fn connect() -> rpc::Result<Self> {
                let client = rpc::McpClient::builder()
                    .endpoint(#endpoint)
                    .build()
                    .await?;
                Ok(Self { client })
            }

            pub async fn connect_with_endpoint(endpoint: &str) -> rpc::Result<Self> {
                let client = rpc::McpClient::builder()
                    .endpoint(endpoint)
                    .build()
                    .await?;
                Ok(Self { client })
            }

            pub fn mcp_client(&self) -> &rpc::McpClient {
                &self.client
            }

            #(#methods)*
        }
    };

    TokenStream::from(expanded)
}

pub fn impl_mcp_tool(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}
