use std::{
    collections::{BTreeMap, HashMap},
    sync::{Arc, Mutex},
};

use foldhash::fast::RandomState;
use indexmap::IndexMap;
use openapiv3::{
    Components, MediaType, OpenAPI, Operation, Paths, ReferenceOr, RequestBody, Response,
    Responses, StatusCode,
};
use schemars::{JsonSchema, Schema, SchemaGenerator};
use serde::{Deserialize, Serialize};

#[cfg(feature = "rdma")]
use crate::rdma::RdmaService;
use crate::{
    Context, Payload,
    error::{Error, ErrorKind, Result},
    services::MetaService,
};

/// Type alias for service method handler functions.
type Func = Box<dyn Fn(Context, Payload) -> Result<()> + Send + Sync>;

/// JSON schema information for a service method.
///
/// Contains the request and response schemas used for OpenAPI generation
/// and runtime validation.
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
pub struct MethodInfo {
    /// JSON schema for the request type.
    pub req_schema: Schema,
    /// JSON schema for the response type.
    pub rsp_schema: Schema,
}

/// Internal method representation containing metadata and handler.
pub struct Method {
    /// Method schema information.
    pub info: MethodInfo,
    /// The actual handler function for this method.
    func: Func,
}

/// Service method router and registry.
///
/// The `Router` is responsible for:
/// - Registering service methods and their handlers
/// - Generating OpenAPI specifications
/// - Dispatching incoming requests to the appropriate handlers
/// - Managing JSON schemas for request/response types
///
/// # Examples
///
/// ```rust,ignore
/// let mut router = Router::default();
/// EchoService::ruapc_export(Arc::new(EchoImpl), &mut router);
/// ```
pub struct Router {
    /// Schema generator for JSON schemas.
    pub generator: Mutex<SchemaGenerator>,
    /// Registered methods mapped by name (e.g., "ServiceName/method_name").
    pub methods: HashMap<String, Method, RandomState>,
    /// Generated OpenAPI specification.
    pub openapi: OpenAPI,
}

impl Default for Router {
    fn default() -> Self {
        let settings = schemars::generate::SchemaSettings::openapi3();
        let mut this = Self {
            generator: Mutex::new(SchemaGenerator::new(settings)),
            methods: HashMap::default(),
            openapi: OpenAPI::default(),
        };
        MetaService::ruapc_export(Arc::new(()), &mut this);
        #[cfg(feature = "rdma")]
        RdmaService::ruapc_export(Arc::new(()), &mut this);
        this
    }
}

impl Router {
    /// Adds a method to the router.
    ///
    /// This method registers a service method handler along with its request and response schemas.
    /// It's typically called by the code generated by the `#[ruapc::service]` macro.
    ///
    /// # Type Parameters
    ///
    /// * `Req` - The request type with a JSON schema
    /// * `Rsp` - The response type with a JSON schema
    ///
    /// # Arguments
    ///
    /// * `name` - The fully qualified method name (e.g., "ServiceName/method_name")
    /// * `func` - The handler function for this method
    pub fn add_method<Req, Rsp>(&mut self, name: &str, func: Func)
    where
        Req: JsonSchema,
        Rsp: JsonSchema,
    {
        let (req_schema, rsp_schema) = {
            let mut generator = self.generator.lock().unwrap();
            (
                generator.subschema_for::<Req>(),
                generator.subschema_for::<Rsp>(),
            )
        };

        self.methods.insert(
            name.to_string(),
            Method {
                info: MethodInfo {
                    req_schema,
                    rsp_schema,
                },
                func,
            },
        );
    }

    /// Builds the OpenAPI specification from registered methods.
    ///
    /// This method generates a complete OpenAPI 3.0 specification including:
    /// - All registered methods as POST endpoints
    /// - Request and response schemas
    /// - Component schemas for all referenced types
    ///
    /// # Errors
    ///
    /// Returns an error if schema conversion to JSON fails.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// # use ruapc::Router;
    /// # let mut router = Router::default();
    /// router.build_open_api().unwrap();
    /// let openapi_json = serde_json::to_string_pretty(&router.openapi).unwrap();
    /// ```
    pub fn build_open_api(&mut self) -> Result<()> {
        let mut paths = BTreeMap::new();
        for (name, method) in &self.methods {
            let request_schema = serde_json::to_value(&method.info.req_schema)?;
            let response_schema = serde_json::to_value(&method.info.rsp_schema)?;

            let request_body = RequestBody {
                content: {
                    IndexMap::from([(
                        "application/json".to_string(),
                        MediaType {
                            schema: Some(serde_json::from_value(request_schema)?),
                            ..Default::default()
                        },
                    )])
                },
                required: true,
                ..Default::default()
            };

            let response = Response {
                content: {
                    IndexMap::from([(
                        "application/json".to_string(),
                        MediaType {
                            schema: Some(serde_json::from_value(response_schema)?),
                            ..Default::default()
                        },
                    )])
                },
                ..Default::default()
            };

            let operation = Operation {
                operation_id: Some(format!("/{name}")),
                request_body: Some(ReferenceOr::Item(request_body)),
                responses: Responses {
                    responses: IndexMap::from([(
                        StatusCode::Code(200),
                        ReferenceOr::Item(response),
                    )]),
                    ..Default::default()
                },
                ..Default::default()
            };

            let path_item = openapiv3::PathItem {
                post: Some(operation),
                ..Default::default()
            };

            paths.insert(format!("/{name}"), ReferenceOr::Item(path_item));
        }

        let mut generator = self.generator.lock().unwrap();
        let definitions = generator.take_definitions(true);
        let schemas = definitions
            .into_iter()
            .map(|(name, schema)| Ok((name, serde_json::from_value(schema)?)))
            .collect::<Result<IndexMap<_, _>>>()?;

        // Create base OpenAPI specification with natural builder pattern
        self.openapi = OpenAPI {
            openapi: "3.0.0".to_string(),
            components: Some(Components {
                schemas,
                ..Default::default()
            }),
            paths: Paths {
                paths: paths.into_iter().collect(),
                ..Default::default()
            },
            ..Default::default()
        };

        Ok(())
    }

    /// Returns an iterator over all registered method names.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// # use ruapc::Router;
    /// # let router = Router::default();
    /// for method_name in router.method_names() {
    ///     println!("Registered method: {}", method_name);
    /// }
    /// ```
    pub fn method_names(&self) -> impl Iterator<Item = &String> {
        self.methods.keys()
    }

    /// Dispatches an incoming message to the appropriate handler.
    ///
    /// This method:
    /// 1. Looks up the handler for the requested method
    /// 2. Spawns a tokio task to handle the request asynchronously
    /// 3. Sends an error response if the method is not found
    ///
    /// # Arguments
    ///
    /// * `ctx` - The RPC context for this request
    /// * `payload` - The incoming message payload to dispatch
    pub fn dispatch(&self, mut ctx: Context, payload: Payload) {
        if let Some(method) = self.methods.get(&ctx.msg_meta.method) {
            let _ = (method.func)(ctx, payload);
        } else {
            tokio::spawn(async move {
                let m = format!("method not found: {}", ctx.msg_meta.method);
                tracing::error!("{}", m);
                ctx.send_err_rsp(Error::new(ErrorKind::InvalidArgument, m))
                    .await;
            });
        }
    }
}

impl std::fmt::Debug for Router {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Router")
            .field("methods", &self.methods.keys())
            .field("generator", &())
            .finish()
    }
}
