//! Embedder configuration use cases (LOT C2). Each is a single-responsibility
//! struct carrying its ports as `Arc<dyn Port>` and exposing one `execute`.
//!
//! - [`ListEmbedderProfiles`] / [`SaveEmbedderProfile`] / [`DeleteEmbedderProfile`]
//!   — CRUD over the persisted [`EmbedderProfile`]s through the
//!   [`EmbedderProfileStore`] port.
//! - [`DescribeEmbedderEngines`] — a read-only view of the engines available to the
//!   "configure an embedder?" UI: the recommended ONNX model catalogue, a best-effort
//!   snapshot of the local environment ([`EmbedderEnvInspector`]), and which strategies
//!   are actually compiled into this binary.
//!
//! Hexagonal boundary: the application depends on the domain ports and on plain
//! data only. The static engine catalogue and the compiled-capability flags are
//! **injected** at the composition root (the infrastructure owns `reqwest`/`fastembed`,
//! never the application).

use std::sync::Arc;

use domain::ports::{EmbedderEnvInspector, EmbedderProfileStore};
use domain::profile::{EmbedderProfile, EmbedderStrategy};

use crate::error::AppError;

// ---------------------------------------------------------------------------
// ListEmbedderProfiles
// ---------------------------------------------------------------------------

/// Output of [`ListEmbedderProfiles::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListEmbedderProfilesOutput {
    /// All configured embedder profiles (empty when none configured ⇒ `none`).
    pub profiles: Vec<EmbedderProfile>,
}

/// Lists the configured embedder profiles from the store.
pub struct ListEmbedderProfiles {
    store: Arc<dyn EmbedderProfileStore>,
}

impl ListEmbedderProfiles {
    /// Builds the use case from the [`EmbedderProfileStore`] port.
    #[must_use]
    pub fn new(store: Arc<dyn EmbedderProfileStore>) -> Self {
        Self { store }
    }

    /// Lists configured embedder profiles.
    ///
    /// # Errors
    /// [`AppError::Store`] on persistence failure.
    pub async fn execute(&self) -> Result<ListEmbedderProfilesOutput, AppError> {
        Ok(ListEmbedderProfilesOutput {
            profiles: self.store.list().await?,
        })
    }
}

// ---------------------------------------------------------------------------
// SaveEmbedderProfile
// ---------------------------------------------------------------------------

/// Input for [`SaveEmbedderProfile::execute`]: the raw fields of the profile to
/// upsert (validated into an [`EmbedderProfile`] entity).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaveEmbedderProfileInput {
    /// Stable identifier (e.g. `"local-onnx-minilm"`). Non-empty.
    pub id: String,
    /// Display name. Non-empty.
    pub name: String,
    /// Embedding strategy driving which concrete adapter is used.
    pub strategy: EmbedderStrategy,
    /// Model identifier, when the strategy needs one.
    pub model: Option<String>,
    /// Endpoint URL for a server/API strategy.
    pub endpoint: Option<String>,
    /// Name of the env var carrying the API key (never the key itself).
    pub api_key_env: Option<String>,
    /// Length of the vectors this engine produces. Non-zero.
    pub dimension: usize,
}

/// Output of [`SaveEmbedderProfile::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SaveEmbedderProfileOutput {
    /// The saved (validated) profile, echoed back.
    pub profile: EmbedderProfile,
}

/// Persists (creates or replaces by id) a single embedder profile, after building
/// and validating the entity.
pub struct SaveEmbedderProfile {
    store: Arc<dyn EmbedderProfileStore>,
}

impl SaveEmbedderProfile {
    /// Builds the use case from the [`EmbedderProfileStore`] port.
    #[must_use]
    pub fn new(store: Arc<dyn EmbedderProfileStore>) -> Self {
        Self { store }
    }

    /// Validates then saves the profile.
    ///
    /// # Errors
    /// - [`AppError::Invalid`] if the profile's invariants are violated (empty
    ///   `id`/`name`, zero `dimension`),
    /// - [`AppError::Store`] on persistence failure.
    pub async fn execute(
        &self,
        input: SaveEmbedderProfileInput,
    ) -> Result<SaveEmbedderProfileOutput, AppError> {
        let profile = EmbedderProfile::new(
            input.id,
            input.name,
            input.strategy,
            input.model,
            input.endpoint,
            input.api_key_env,
            input.dimension,
        )
        .map_err(|e| AppError::Invalid(e.to_string()))?;
        self.store.save(&profile).await?;
        Ok(SaveEmbedderProfileOutput { profile })
    }
}

// ---------------------------------------------------------------------------
// DeleteEmbedderProfile
// ---------------------------------------------------------------------------

/// Input for [`DeleteEmbedderProfile::execute`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeleteEmbedderProfileInput {
    /// Id of the embedder profile to delete.
    pub id: String,
}

/// Deletes an embedder profile by id.
pub struct DeleteEmbedderProfile {
    store: Arc<dyn EmbedderProfileStore>,
}

impl DeleteEmbedderProfile {
    /// Builds the use case from the [`EmbedderProfileStore`] port.
    #[must_use]
    pub fn new(store: Arc<dyn EmbedderProfileStore>) -> Self {
        Self { store }
    }

    /// Deletes the profile.
    ///
    /// # Errors
    /// [`AppError::NotFound`] if the id is unknown, [`AppError::Store`] on
    /// persistence failure.
    pub async fn execute(&self, input: DeleteEmbedderProfileInput) -> Result<(), AppError> {
        self.store.delete(&input.id).await?;
        Ok(())
    }
}

// ---------------------------------------------------------------------------
// DescribeEmbedderEngines
// ---------------------------------------------------------------------------

/// A recommendable local ONNX model, as a plain application value (mirrors the
/// infrastructure `OnnxModelInfo` data, injected at the composition root so the
/// application never depends on the infrastructure crate).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OnnxModelView {
    /// Stable model id accepted by a `localOnnx` profile's `model` field.
    pub id: String,
    /// Human-readable name for the UI.
    pub display_name: String,
    /// Length of the vectors this model produces.
    pub dimension: usize,
    /// Approximate download/disk size in megabytes.
    pub approx_size_mb: u32,
    /// Whether this is the recommended default model.
    pub recommended: bool,
}

/// A read-only description of the embedding engines available to the
/// "configure an embedder?" UI (C2/C3).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EmbedderEnginesView {
    /// The curated catalogue of recommendable local ONNX models.
    pub recommended_onnx: Vec<OnnxModelView>,
    /// Whether an Ollama-style local embedding server was detected (best-effort).
    pub ollama_detected: bool,
    /// Ids of the recommended ONNX models already present in the local cache.
    pub onnx_cached_models: Vec<String>,
    /// Whether the HTTP capability (`localServer`/`api`) is compiled into this binary.
    pub vector_http_enabled: bool,
    /// Whether the in-process ONNX capability (`localOnnx`) is compiled into this binary.
    pub vector_onnx_enabled: bool,
}

/// Describes the engines available to the embedder-configuration UI: the static
/// ONNX catalogue + compiled-capability flags (injected at construction), enriched
/// with a best-effort live snapshot of the local environment via the
/// [`EmbedderEnvInspector`] port. Read-only — emits no event, never fails on the
/// environment probe (the port is best-effort by contract).
pub struct DescribeEmbedderEngines {
    inspector: Arc<dyn EmbedderEnvInspector>,
    recommended_onnx: Vec<OnnxModelView>,
    vector_http_enabled: bool,
    vector_onnx_enabled: bool,
}

impl DescribeEmbedderEngines {
    /// Builds the use case from the [`EmbedderEnvInspector`] port plus the static
    /// engine catalogue and compiled-capability flags (data owned by infrastructure
    /// and injected at the composition root, so the application stays infra-free).
    #[must_use]
    pub fn new(
        inspector: Arc<dyn EmbedderEnvInspector>,
        recommended_onnx: Vec<OnnxModelView>,
        vector_http_enabled: bool,
        vector_onnx_enabled: bool,
    ) -> Self {
        Self {
            inspector,
            recommended_onnx,
            vector_http_enabled,
            vector_onnx_enabled,
        }
    }

    /// Returns the engines view. Infallible in practice: the environment probe is
    /// best-effort and degrades to "nothing detected".
    ///
    /// # Errors
    /// Never; the `Result` keeps the call site uniform with the other use cases.
    #[allow(clippy::unused_async)]
    pub async fn execute(&self) -> Result<EmbedderEnginesView, AppError> {
        let report = self.inspector.inspect().await;
        Ok(EmbedderEnginesView {
            recommended_onnx: self.recommended_onnx.clone(),
            ollama_detected: report.ollama_detected,
            onnx_cached_models: report.onnx_cached_models,
            vector_http_enabled: self.vector_http_enabled,
            vector_onnx_enabled: self.vector_onnx_enabled,
        })
    }
}
