//! REST binding — maps service methods to HTTP endpoints.
//!
//! Method → endpoint mapping (default convention, overridable):
//!   `get_*`    → GET    /resource/{id}
//!   `list_*`   → GET    /resource
//!   `create_*` → POST   /resource
//!   `update_*` → PUT    /resource/{id}
//!   `delete_*` → DELETE /resource/{id}
//!
//! In production, use `reqwest` for the HTTP client. This implementation
//! builds the request and returns a mock response so the binding logic
//! can be tested without a live server.

use super::{Binding, BindingKind, ServiceRequest, ServiceResponse};
use crate::{config::ServiceConfig, ServiceError, ServiceResult};

/// HTTP REST binding.
pub struct RestBinding {
    pub config: ServiceConfig,
}

impl RestBinding {
    pub fn new(config: ServiceConfig) -> Self {
        Self { config }
    }

    /// Derive the HTTP method from the service method name.
    pub fn http_method(method_name: &str) -> &'static str {
        let lower = method_name.to_lowercase();
        if lower.starts_with("get_") || lower.starts_with("fetch_") || lower.starts_with("find_") {
            "GET"
        } else if lower.starts_with("list_") || lower.starts_with("all_") {
            "GET"
        } else if lower.starts_with("create_") || lower.starts_with("add_") || lower.starts_with("post_") {
            "POST"
        } else if lower.starts_with("update_") || lower.starts_with("edit_") || lower.starts_with("put_") {
            "PUT"
        } else if lower.starts_with("delete_") || lower.starts_with("remove_") {
            "DELETE"
        } else if lower.starts_with("patch_") {
            "PATCH"
        } else {
            "POST"
        }
    }

    /// Derive the URL path from the service name and method name.
    /// `UserService::get_user` → `/users/{id}`
    pub fn url_path(&self, method_name: &str, params: &std::collections::HashMap<String, String>) -> String {
        let base = self.config.base_url.as_deref().unwrap_or("");
        let resource = self.resource_name();
        let http_method = Self::http_method(method_name);

        // Check if there's an ID param
        let id_param = params.get("id").or_else(|| {
            params.values().next()
        });

        match (http_method, id_param) {
            ("GET" | "PUT" | "DELETE", Some(id)) => {
                format!("{}/{}/{}", base, resource, id)
            }
            _ => format!("{}/{}", base, resource),
        }
    }

    /// Derive the REST resource name from the service name.
    /// `UserService` → `users`, `OrderService` → `orders`
    fn resource_name(&self) -> String {
        let name = self.config.name.trim_end_matches("Service");
        let lower = name.to_lowercase();
        // Simple pluralization: append 's' (good enough for scaffolding)
        if lower.ends_with('s') {
            lower
        } else {
            format!("{}s", lower)
        }
    }

    /// Build auth headers for the request.
    pub fn auth_headers(&self) -> Vec<(String, String)> {
        let mut headers = Vec::new();
        if let Some(auth_header) = self.config.auth.authorization_header() {
            headers.push(("Authorization".to_string(), auth_header));
        }
        if let Some((key, val)) = self.config.auth.api_key_header() {
            headers.push((key, val));
        }
        headers
    }

    /// Build the full request description (for logging/testing without live HTTP).
    pub fn build_request_description(
        &self,
        request: &ServiceRequest,
    ) -> String {
        let http_method = Self::http_method(&request.method);
        let url = self.url_path(&request.method, &request.params);
        let headers = self.auth_headers();
        let body = if matches!(http_method, "POST" | "PUT" | "PATCH") {
            serde_params_to_json(&request.params)
        } else {
            String::new()
        };
        format!(
            "{} {}\nHeaders: {:?}\nBody: {}",
            http_method, url, headers, body
        )
    }
}

/// Minimal JSON serialization for params (no serde dependency).
pub(crate) fn serde_params_to_json(params: &std::collections::HashMap<String, String>) -> String {
    let fields: Vec<String> = params
        .iter()
        .map(|(k, v)| format!("\"{}\":\"{}\"", k, v.replace('"', "\\\"")))
        .collect();
    format!("{{{}}}", fields.join(","))
}

impl Binding for RestBinding {
    fn kind(&self) -> BindingKind {
        BindingKind::Rest
    }

    fn call(&self, request: ServiceRequest) -> ServiceResult<ServiceResponse> {
        if self.config.base_url.is_none() {
            return Err(ServiceError::Binding(
                "REST binding requires base_url in config".into(),
            ));
        }

        let _description = self.build_request_description(&request);

        // In production: use reqwest to make the actual HTTP call:
        //   let client = reqwest::blocking::Client::new();
        //   let resp = client.request(http_method, &url).headers(...).body(body).send()?;
        //   return Ok(ServiceResponse { status: resp.status().as_u16(), body: resp.text()?, ... });

        // For the framework layer: return a structured mock response so the
        // binding selection, URL derivation, and auth logic can all be tested.
        Ok(ServiceResponse::ok(format!(
            "{{\"service\":\"{}\",\"method\":\"{}\"}}",
            request.service, request.method
        )))
    }
}
