//! External Component Model Interface
//!
//! This module provides support for external component models via:
//! - Dynamic library loading (.dll/.so) via FFI
//! - HTTP API calls to external services
//!
//! ## Architecture
//!
//! The external model interface allows integration of proprietary or vendor-supplied
//! component models that cannot be implemented natively in Rust.
//!
//! ## FFI Interface (DLL/SO)
//!
//! External libraries must implement the `entropyk_model` C ABI:
//!
//! ```c
//! // Required exported functions:
//! int entropyk_model_compute(double* inputs, double* outputs, int n_in, int n_out);
//! int entropyk_model_jacobian(double* inputs, double* jacobian, int n_in, int n_out);
//! const char* entropyk_model_name(void);
//! const char* entropyk_model_version(void);
//! ```
//!
//! ## HTTP API Interface
//!
//! External services must provide REST endpoints:
//!
//! - `POST /compute`: Accepts JSON with inputs, returns JSON with outputs
//! - `POST /jacobian`: Accepts JSON with inputs, returns JSON with Jacobian matrix

use crate::ComponentError;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;

/// Configuration for an external model.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalModelConfig {
    /// Unique identifier for this model
    pub id: String,
    /// Model type (ffi or http)
    pub model_type: ExternalModelType,
    /// Number of inputs expected
    pub n_inputs: usize,
    /// Number of outputs produced
    pub n_outputs: usize,
    /// Optional timeout in milliseconds
    #[serde(default = "default_timeout")]
    pub timeout_ms: u64,
}

fn default_timeout() -> u64 {
    5000
}

/// Type of external model interface.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ExternalModelType {
    /// Dynamic library (.dll on Windows, .so on Linux, .dylib on macOS)
    Ffi {
        /// Path to the library file
        library_path: PathBuf,
        /// Optional function name prefix
        function_prefix: Option<String>,
    },
    /// HTTP REST API
    Http {
        /// Base URL for the API
        base_url: String,
        /// Optional API key for authentication
        api_key: Option<String>,
    },
}

/// Trait for external model implementations.
///
/// This trait abstracts over FFI and HTTP interfaces, providing
/// a unified interface for the solver.
pub trait ExternalModel: Send + Sync {
    /// Returns the model identifier.
    fn id(&self) -> &str;

    /// Returns the number of inputs.
    fn n_inputs(&self) -> usize;

    /// Returns the number of outputs.
    fn n_outputs(&self) -> usize;

    /// Computes outputs from inputs.
    ///
    /// # Arguments
    ///
    /// * `inputs` - Input values (length = n_inputs)
    ///
    /// # Returns
    ///
    /// Output values (length = n_outputs)
    fn compute(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError>;

    /// Computes the Jacobian matrix.
    ///
    /// # Arguments
    ///
    /// * `inputs` - Input values
    ///
    /// # Returns
    ///
    /// Jacobian matrix as a flat array (row-major, n_outputs × n_inputs)
    fn jacobian(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError>;

    /// Returns model metadata.
    fn metadata(&self) -> ExternalModelMetadata;
}

/// Metadata about an external model.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExternalModelMetadata {
    /// Model name
    pub name: String,
    /// Model version
    pub version: String,
    /// Model description
    pub description: Option<String>,
    /// Input names/units
    pub input_names: Vec<String>,
    /// Output names/units
    pub output_names: Vec<String>,
}

/// Errors from external model operations.
#[derive(Debug, Clone, thiserror::Error)]
pub enum ExternalModelError {
    #[error("Invalid input format: {0}")]
    /// Documentation pending
    InvalidInput(String),
    #[error("Invalid output format: {0}")]
    /// Documentation pending
    InvalidOutput(String),
    /// Library loading failed
    #[error("Failed to load library: {0}")]
    LibraryLoad(String),

    /// Function not found in library
    #[error("Function not found: {0}")]
    FunctionNotFound(String),

    /// Computation failed
    #[error("Computation failed: {0}")]
    ComputationFailed(String),

    /// Invalid input dimensions
    #[error("Invalid input dimensions: expected {expected}, got {actual}")]
    InvalidInputDimensions {
        /// Expected number of inputs
        expected: usize,
        /// Actual number received
        actual: usize,
    },

    /// HTTP request failed
    #[error("HTTP request failed: {0}")]
    HttpError(String),

    /// Timeout exceeded
    #[error("Operation timed out after {0}ms")]
    Timeout(u64),

    /// JSON parsing error
    #[error("JSON error: {0}")]
    JsonError(String),

    /// Model not initialized
    #[error("Model not initialized")]
    NotInitialized,
}

impl From<ExternalModelError> for ComponentError {
    fn from(err: ExternalModelError) -> Self {
        // Preserve error type information for programmatic handling
        match &err {
            ExternalModelError::LibraryLoad(msg) => {
                ComponentError::InvalidState(format!("External model library load failed: {}", msg))
            }
            ExternalModelError::HttpError(msg) => {
                ComponentError::InvalidState(format!("External model HTTP error: {}", msg))
            }
            ExternalModelError::InvalidInput(msg) => {
                ComponentError::InvalidState(format!("External model invalid input: {}", msg))
            }
            ExternalModelError::InvalidOutput(msg) => {
                ComponentError::InvalidState(format!("External model invalid output: {}", msg))
            }
            ExternalModelError::Timeout(msg) => {
                ComponentError::InvalidState(format!("External model timeout: {}", msg))
            }
            ExternalModelError::NotInitialized => {
                ComponentError::InvalidState("External model not initialized".to_string())
            }
            _ => ComponentError::InvalidState(format!("External model error: {}", err)),
        }
    }
}

/// Request body for HTTP compute endpoint.
#[derive(Debug, Serialize)]
#[allow(dead_code)]
struct ComputeRequest {
    inputs: Vec<f64>,
}

/// Response from HTTP compute endpoint.
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct ComputeResponse {
    outputs: Vec<f64>,
}

/// Request body for HTTP Jacobian endpoint.
#[derive(Debug, Serialize)]
#[allow(dead_code)]
struct JacobianRequest {
    inputs: Vec<f64>,
}

/// Response from HTTP Jacobian endpoint.
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct JacobianResponse {
    jacobian: Vec<f64>,
}

/// FFI-based external model (mock implementation for non-ffi builds).
///
/// When the `ffi` feature is not enabled, this provides a mock implementation
/// that can be used for testing and development. The mock passes inputs through
/// unchanged (identity function).
#[cfg(not(feature = "ffi"))]
pub struct FfiModel {
    config: ExternalModelConfig,
    metadata: ExternalModelMetadata,
}

#[cfg(not(feature = "ffi"))]
impl FfiModel {
    /// Creates a new FFI model (mock implementation without ffi feature).
    ///
    /// This creates a mock model that can be used for testing. The mock
    /// implements an identity function (output = input for first n_outputs).
    pub fn new(config: ExternalModelConfig) -> Result<Self, ExternalModelError> {
        let metadata = ExternalModelMetadata {
            name: format!("Mock FFI Model: {}", config.id),
            version: "0.1.0-mock".to_string(),
            description: Some("Mock FFI model for testing (ffi feature not enabled)".to_string()),
            input_names: (0..config.n_inputs)
                .map(|i| format!("input_{}", i))
                .collect(),
            output_names: (0..config.n_outputs)
                .map(|i| format!("output_{}", i))
                .collect(),
        };
        Ok(Self { config, metadata })
    }

    /// Creates with custom mock metadata for testing.
    pub fn new_mock(
        config: ExternalModelConfig,
        metadata: ExternalModelMetadata,
    ) -> Result<Self, ExternalModelError> {
        Ok(Self { config, metadata })
    }
}

#[cfg(not(feature = "ffi"))]
impl ExternalModel for FfiModel {
    fn id(&self) -> &str {
        &self.config.id
    }

    fn n_inputs(&self) -> usize {
        self.config.n_inputs
    }

    fn n_outputs(&self) -> usize {
        self.config.n_outputs
    }

    fn compute(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        if inputs.len() != self.config.n_inputs {
            return Err(ExternalModelError::InvalidInputDimensions {
                expected: self.config.n_inputs,
                actual: inputs.len(),
            });
        }
        // Mock: pass through inputs (identity for first n_outputs, zero padding)
        let mut outputs = vec![0.0; self.config.n_outputs];
        for (i, &input) in inputs.iter().take(self.config.n_outputs).enumerate() {
            outputs[i] = input;
        }
        Ok(outputs)
    }

    fn jacobian(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        if inputs.len() != self.config.n_inputs {
            return Err(ExternalModelError::InvalidInputDimensions {
                expected: self.config.n_inputs,
                actual: inputs.len(),
            });
        }
        // Mock: returns identity-like Jacobian
        let mut jacobian = vec![0.0; self.config.n_inputs * self.config.n_outputs];
        let min_dim = self.config.n_inputs.min(self.config.n_outputs);
        for i in 0..min_dim {
            jacobian[i * self.config.n_inputs + i] = 1.0;
        }
        Ok(jacobian)
    }

    fn metadata(&self) -> ExternalModelMetadata {
        self.metadata.clone()
    }
}

/// HTTP-based external model (mock implementation for non-http builds).
///
/// When the `http` feature is not enabled, this provides a mock implementation
/// that can be used for testing and development. The mock passes inputs through
/// unchanged (identity function).
#[cfg(not(feature = "http"))]
pub struct HttpModel {
    config: ExternalModelConfig,
    metadata: ExternalModelMetadata,
}

#[cfg(not(feature = "http"))]
impl HttpModel {
    /// Creates a new HTTP model (mock implementation without http feature).
    ///
    /// This creates a mock model that can be used for testing. The mock
    /// implements an identity function (output = input for first n_outputs).
    pub fn new(config: ExternalModelConfig) -> Result<Self, ExternalModelError> {
        let metadata = ExternalModelMetadata {
            name: format!("Mock HTTP Model: {}", config.id),
            version: "0.1.0-mock".to_string(),
            description: Some("Mock HTTP model for testing (http feature not enabled)".to_string()),
            input_names: (0..config.n_inputs)
                .map(|i| format!("input_{}", i))
                .collect(),
            output_names: (0..config.n_outputs)
                .map(|i| format!("output_{}", i))
                .collect(),
        };
        Ok(Self { config, metadata })
    }

    /// Creates with custom mock metadata for testing.
    pub fn new_mock(
        config: ExternalModelConfig,
        metadata: ExternalModelMetadata,
    ) -> Result<Self, ExternalModelError> {
        Ok(Self { config, metadata })
    }
}

#[cfg(not(feature = "http"))]
impl ExternalModel for HttpModel {
    fn id(&self) -> &str {
        &self.config.id
    }

    fn n_inputs(&self) -> usize {
        self.config.n_inputs
    }

    fn n_outputs(&self) -> usize {
        self.config.n_outputs
    }

    fn compute(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        if inputs.len() != self.config.n_inputs {
            return Err(ExternalModelError::InvalidInputDimensions {
                expected: self.config.n_inputs,
                actual: inputs.len(),
            });
        }
        // Mock: pass through inputs (identity for first n_outputs, zero padding)
        let mut outputs = vec![0.0; self.config.n_outputs];
        for (i, &input) in inputs.iter().take(self.config.n_outputs).enumerate() {
            outputs[i] = input;
        }
        Ok(outputs)
    }

    fn jacobian(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        if inputs.len() != self.config.n_inputs {
            return Err(ExternalModelError::InvalidInputDimensions {
                expected: self.config.n_inputs,
                actual: inputs.len(),
            });
        }
        // Mock: returns identity-like Jacobian
        let mut jacobian = vec![0.0; self.config.n_inputs * self.config.n_outputs];
        let min_dim = self.config.n_inputs.min(self.config.n_outputs);
        for i in 0..min_dim {
            jacobian[i * self.config.n_inputs + i] = 1.0;
        }
        Ok(jacobian)
    }

    fn metadata(&self) -> ExternalModelMetadata {
        self.metadata.clone()
    }
}

/// Thread-safe wrapper for external models.
///
/// This wrapper ensures safe concurrent access to external models,
/// which may not be thread-safe themselves.
pub struct ThreadSafeExternalModel {
    inner: Arc<dyn ExternalModel>,
}

impl ThreadSafeExternalModel {
    /// Creates a new thread-safe wrapper.
    pub fn new(model: impl ExternalModel + 'static) -> Self {
        Self {
            inner: Arc::new(model),
        }
    }

    /// Creates from an existing Arc.
    pub fn from_arc(model: Arc<dyn ExternalModel>) -> Self {
        Self { inner: model }
    }

    /// Returns a reference to the inner model.
    pub fn inner(&self) -> &dyn ExternalModel {
        self.inner.as_ref()
    }
}

impl Clone for ThreadSafeExternalModel {
    fn clone(&self) -> Self {
        Self {
            inner: Arc::clone(&self.inner),
        }
    }
}

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

/// Mock external model for testing.
#[derive(Debug, Clone)]
pub struct MockExternalModel {
    id: String,
    n_inputs: usize,
    n_outputs: usize,
    compute_fn: fn(&[f64]) -> Vec<f64>,
}

impl MockExternalModel {
    /// Creates a new mock model.
    pub fn new(
        id: impl Into<String>,
        n_inputs: usize,
        n_outputs: usize,
        compute_fn: fn(&[f64]) -> Vec<f64>,
    ) -> Self {
        Self {
            id: id.into(),
            n_inputs,
            n_outputs,
            compute_fn,
        }
    }

    /// Creates a simple linear model: y = x
    pub fn linear_passthrough(n: usize) -> Self {
        Self::new("linear_passthrough", n, n, |x| x.to_vec())
    }

    /// Creates a model that doubles inputs.
    pub fn doubler(n: usize) -> Self {
        Self::new("doubler", n, n, |x| x.iter().map(|v| v * 2.0).collect())
    }
}

impl ExternalModel for MockExternalModel {
    fn id(&self) -> &str {
        &self.id
    }

    fn n_inputs(&self) -> usize {
        self.n_inputs
    }

    fn n_outputs(&self) -> usize {
        self.n_outputs
    }

    fn compute(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        if inputs.len() != self.n_inputs {
            return Err(ExternalModelError::InvalidInputDimensions {
                expected: self.n_inputs,
                actual: inputs.len(),
            });
        }
        Ok((self.compute_fn)(inputs))
    }

    fn jacobian(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        // Default: finite difference approximation
        let h = 1e-6;
        let mut jacobian = vec![0.0; self.n_outputs * self.n_inputs];

        for j in 0..self.n_inputs {
            let mut inputs_plus = inputs.to_vec();
            let mut inputs_minus = inputs.to_vec();
            inputs_plus[j] += h;
            inputs_minus[j] -= h;

            let y_plus = self.compute(&inputs_plus)?;
            let y_minus = self.compute(&inputs_minus)?;

            for i in 0..self.n_outputs {
                jacobian[i * self.n_inputs + j] = (y_plus[i] - y_minus[i]) / (2.0 * h);
            }
        }

        Ok(jacobian)
    }

    fn metadata(&self) -> ExternalModelMetadata {
        ExternalModelMetadata {
            name: self.id.clone(),
            version: "1.0.0".to_string(),
            description: Some("Mock external model for testing".to_string()),
            input_names: (0..self.n_inputs).map(|i| format!("input_{}", i)).collect(),
            output_names: (0..self.n_outputs)
                .map(|i| format!("output_{}", i))
                .collect(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_mock_external_model_compute() {
        let model = MockExternalModel::doubler(3);
        let result = model.compute(&[1.0, 2.0, 3.0]).unwrap();
        assert_eq!(result, vec![2.0, 4.0, 6.0]);
    }

    #[test]
    fn test_mock_external_model_dimensions() {
        let model = MockExternalModel::doubler(3);
        assert_eq!(model.n_inputs(), 3);
        assert_eq!(model.n_outputs(), 3);
    }

    #[test]
    fn test_mock_external_model_invalid_input() {
        let model = MockExternalModel::doubler(3);
        let result = model.compute(&[1.0, 2.0]);
        assert!(result.is_err());
    }

    #[test]
    fn test_mock_external_model_jacobian() {
        let model = MockExternalModel::doubler(2);
        let jac = model.jacobian(&[1.0, 2.0]).unwrap();

        // Jacobian of y = 2x should be [[2, 0], [0, 2]]
        assert!((jac[0] - 2.0).abs() < 0.01);
        assert!((jac[1] - 0.0).abs() < 0.01);
        assert!((jac[2] - 0.0).abs() < 0.01);
        assert!((jac[3] - 2.0).abs() < 0.01);
    }

    #[test]
    fn test_thread_safe_wrapper() {
        let model = MockExternalModel::doubler(2);
        let wrapped = ThreadSafeExternalModel::new(model);

        let result = wrapped.inner().compute(&[1.0, 2.0]).unwrap();
        assert_eq!(result, vec![2.0, 4.0]);
    }

    #[test]
    fn test_thread_safe_clone() {
        let model = MockExternalModel::doubler(2);
        let wrapped = ThreadSafeExternalModel::new(model);
        let cloned = wrapped.clone();

        assert_eq!(wrapped.inner().id(), cloned.inner().id());
    }

    #[test]
    fn test_external_model_metadata() {
        let model = MockExternalModel::doubler(2);
        let meta = model.metadata();

        assert_eq!(meta.name, "doubler");
        assert_eq!(meta.version, "1.0.0");
        assert_eq!(meta.input_names, vec!["input_0", "input_1"]);
        assert_eq!(meta.output_names, vec!["output_0", "output_1"]);
    }

    #[test]
    fn test_linear_passthrough_model() {
        let model = MockExternalModel::linear_passthrough(3);
        let result = model.compute(&[1.0, 2.0, 3.0]).unwrap();
        assert_eq!(result, vec![1.0, 2.0, 3.0]);
    }

    #[test]
    fn test_external_model_config() {
        let config = ExternalModelConfig {
            id: "test_model".to_string(),
            model_type: ExternalModelType::Http {
                base_url: "http://localhost:8080".to_string(),
                api_key: Some("secret".to_string()),
            },
            n_inputs: 4,
            n_outputs: 2,
            timeout_ms: 3000,
        };

        assert_eq!(config.id, "test_model");
        assert_eq!(config.n_inputs, 4);
        assert_eq!(config.n_outputs, 2);
        assert_eq!(config.timeout_ms, 3000);
    }

    #[test]
    fn test_error_conversion() {
        let err = ExternalModelError::ComputationFailed("test error".to_string());
        let component_err: ComponentError = err.into();

        match component_err {
            ComponentError::InvalidState(msg) => {
                assert!(msg.contains("External model error"));
            }
            _ => panic!("Expected InvalidState error"),
        }
    }
}

#[cfg(feature = "ffi")]
/// FFI-based external model mapping to a dynamnic library.
pub struct FfiModel {
    config: ExternalModelConfig,
    metadata: ExternalModelMetadata,
    _lib: Arc<libloading::Library>,
}

#[cfg(feature = "ffi")]
impl FfiModel {
    /// Creates a new FFI model by loading a dynamic library.
    pub fn new(config: ExternalModelConfig) -> Result<Self, ExternalModelError> {
        let path = match &config.model_type {
            ExternalModelType::Ffi { library_path, .. } => library_path,
            _ => return Err(ExternalModelError::NotInitialized),
        };

        // Validate library path for security
        Self::validate_library_path(path)?;

        // Safety: Library loading is inherently unsafe. Path has been validated.
        let lib = unsafe { libloading::Library::new(path) }
            .map_err(|e| ExternalModelError::LibraryLoad(e.to_string()))?;

        let metadata = ExternalModelMetadata {
            name: config.id.clone(),
            version: "1.0.0".to_string(), // In a real model, this would be queried from DLL
            description: Some("Real FFI model".to_string()),
            input_names: (0..config.n_inputs).map(|i| format!("in_{}", i)).collect(),
            output_names: (0..config.n_outputs)
                .map(|i| format!("out_{}", i))
                .collect(),
        };

        Ok(Self {
            config,
            metadata,
            _lib: Arc::new(lib),
        })
    }

    /// Validates the library path for security.
    ///
    /// Checks for:
    /// - Path traversal attempts (../, ..\)
    /// - Absolute paths to system directories
    /// - Path canonicalization to prevent symlink attacks
    fn validate_library_path(path: &str) -> Result<PathBuf, ExternalModelError> {
        use std::path::Path;

        let path = Path::new(path);

        // Check for path traversal attempts
        let path_str = path.to_string_lossy();
        if path_str.contains("..") {
            return Err(ExternalModelError::LibraryLoad(
                "Path traversal not allowed in library path".to_string(),
            ));
        }

        // Canonicalize to resolve symlinks and get absolute path
        let canonical = path
            .canonicalize()
            .map_err(|e| ExternalModelError::LibraryLoad(format!("Invalid path: {}", e)))?;

        // Optional: Restrict to specific directories (uncomment and customize as needed)
        // let allowed_dirs = ["/usr/local/lib/entropyk", "./plugins"];
        // let is_allowed = allowed_dirs.iter().any(|dir| {
        //     canonical.starts_with(dir)
        // });
        // if !is_allowed {
        //     return Err(ExternalModelError::LibraryLoad(
        //         "Library path outside allowed directories".to_string(),
        //     ));
        // }

        Ok(canonical)
    }
}

#[cfg(feature = "ffi")]
impl ExternalModel for FfiModel {
    fn id(&self) -> &str {
        &self.config.id
    }
    fn n_inputs(&self) -> usize {
        self.config.n_inputs
    }
    fn n_outputs(&self) -> usize {
        self.config.n_outputs
    }
    fn compute(&self, _inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        // Stub implementation
        unimplemented!("Real FFI compute not fully implemented yet")
    }
    fn jacobian(&self, _inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        unimplemented!("Real FFI jacobian not fully implemented yet")
    }
    fn metadata(&self) -> ExternalModelMetadata {
        self.metadata.clone()
    }
}

#[cfg(feature = "http")]
/// HTTP-based external model mapping to a remote REST service.
pub struct HttpModel {
    config: ExternalModelConfig,
    metadata: ExternalModelMetadata,
    client: reqwest::blocking::Client,
}

#[cfg(feature = "http")]
impl HttpModel {
    /// Creates a new HTTP model with a configurable `reqwest` client.
    pub fn new(config: ExternalModelConfig) -> Result<Self, ExternalModelError> {
        let client = reqwest::blocking::Client::builder()
            .timeout(std::time::Duration::from_millis(config.timeout_ms))
            .build()
            .map_err(|e| ExternalModelError::HttpError(e.to_string()))?;

        let metadata = ExternalModelMetadata {
            name: config.id.clone(),
            version: "1.0.0".to_string(),
            description: Some("Real HTTP model".to_string()),
            input_names: (0..config.n_inputs).map(|i| format!("in_{}", i)).collect(),
            output_names: (0..config.n_outputs)
                .map(|i| format!("out_{}", i))
                .collect(),
        };

        Ok(Self {
            config,
            metadata,
            client,
        })
    }
}

#[cfg(feature = "http")]
impl ExternalModel for HttpModel {
    fn id(&self) -> &str {
        &self.config.id
    }
    fn n_inputs(&self) -> usize {
        self.config.n_inputs
    }
    fn n_outputs(&self) -> usize {
        self.config.n_outputs
    }
    fn compute(&self, inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        let (base_url, api_key) = match &self.config.model_type {
            ExternalModelType::Http { base_url, api_key } => (base_url, api_key),
            _ => return Err(ExternalModelError::NotInitialized),
        };

        let request = ComputeRequest {
            inputs: inputs.to_vec(),
        };
        let mut req_builder = self
            .client
            .post(format!("{}/compute", base_url))
            .json(&request);

        if let Some(key) = api_key {
            req_builder = req_builder.header("Authorization", format!("Bearer {}", key));
        }

        let response = req_builder
            .send()
            .map_err(|e| ExternalModelError::HttpError(e.to_string()))?;
        let result: ComputeResponse = response
            .json()
            .map_err(|e| ExternalModelError::JsonError(e.to_string()))?;

        Ok(result.outputs)
    }
    fn jacobian(&self, _inputs: &[f64]) -> Result<Vec<f64>, ExternalModelError> {
        unimplemented!("Real HTTP jacobian not fully implemented yet")
    }
    fn metadata(&self) -> ExternalModelMetadata {
        self.metadata.clone()
    }
}
