//! Configuration service gRPC implementation
//!
//! Wraps the `configuration` PostgreSQL table, exposing it through the
//! `ConfigService` proto API.  Only `GetConfiguration` and
//! `UpdateConfiguration` are backed by real queries — the remaining RPCs
//! return `UNIMPLEMENTED` until they are needed.

use crate::proto::config::{
    config_service_server::ConfigService, ConfigChangeEvent, ConfigDataType,
    ConfigurationCategory, ConfigurationSetting, DeleteConfigurationRequest,
    DeleteConfigurationResponse, ExportConfigurationRequest, ExportConfigurationResponse,
    GetConfigSchemaRequest, GetConfigSchemaResponse, GetConfigurationHistoryRequest,
    GetConfigurationHistoryResponse, GetConfigurationRequest, GetConfigurationResponse,
    ImportConfigurationRequest, ImportConfigurationResponse, ListCategoriesRequest,
    ListCategoriesResponse, RollbackConfigurationRequest, RollbackConfigurationResponse,
    StreamConfigChangesRequest, UpdateConfigSchemaRequest, UpdateConfigSchemaResponse,
    UpdateConfigurationRequest, UpdateConfigurationResponse, ValidateConfigurationRequest,
    ValidateConfigurationResponse,
};
use sqlx::{PgPool, Row};
use tonic::{Request, Response, Status};
use tracing::instrument;

/// Config service backed by the `configuration` table.
#[derive(Debug, Clone)]
pub struct ConfigServiceImpl {
    pool: PgPool,
}

impl ConfigServiceImpl {
    pub fn new(pool: PgPool) -> Self {
        Self { pool }
    }
}

/// Map DB `value_type` string to the proto enum.
fn map_value_type(vt: &str) -> i32 {
    match vt {
        "number" => ConfigDataType::Number as i32,
        "boolean" => ConfigDataType::Boolean as i32,
        "json" => ConfigDataType::Json as i32,
        _ => ConfigDataType::String as i32,
    }
}

#[tonic::async_trait]
impl ConfigService for ConfigServiceImpl {
    // -----------------------------------------------------------------------
    // GetConfiguration — the dashboard's primary read path
    // -----------------------------------------------------------------------
    #[instrument(skip_all)]
    async fn get_configuration(
        &self,
        request: Request<GetConfigurationRequest>,
    ) -> Result<Response<GetConfigurationResponse>, Status> {
        let req = request.into_inner();

        // Build a dynamic query with optional filters
        let mut query = String::from(
            "SELECT id, key, value, value_type, description, category, \
             environment, is_sensitive, created_at, updated_at \
             FROM configuration WHERE 1=1",
        );
        let mut binds: Vec<String> = Vec::new();

        if let Some(ref cat) = req.category {
            if !cat.is_empty() {
                binds.push(cat.clone());
                query.push_str(&format!(" AND category = ${}", binds.len()));
            }
        }
        if let Some(ref key) = req.key {
            if !key.is_empty() {
                binds.push(key.clone());
                query.push_str(&format!(" AND key = ${}", binds.len()));
            }
        }
        if let Some(ref env) = req.environment {
            if !env.is_empty() {
                binds.push(env.clone());
                query.push_str(&format!(" AND environment = ${}", binds.len()));
            }
        }

        query.push_str(" ORDER BY category, key");

        let mut q = sqlx::query(&query);
        for b in &binds {
            q = q.bind(b);
        }

        let rows = q.fetch_all(&self.pool).await.map_err(|e| {
            Status::internal(format!("DB error: {e}"))
        })?;

        let settings = rows
            .iter()
            .map(|row| {
                let id: i32 = row.get("id");
                let key: String = row.get("key");
                let value: String = row.get("value");
                let value_type: String = row.get("value_type");
                let description: Option<String> = row.get("description");
                let category: Option<String> = row.get("category");
                let is_sensitive: bool = row.get("is_sensitive");
                let created_at: chrono::DateTime<chrono::Utc> = row.get("created_at");
                let updated_at: chrono::DateTime<chrono::Utc> = row.get("updated_at");

                ConfigurationSetting {
                    id: i64::from(id),
                    category: category.unwrap_or_default(),
                    key,
                    value,
                    data_type: map_value_type(&value_type),
                    hot_reload: false,
                    description: description.unwrap_or_default(),
                    default_value: None,
                    required: false,
                    sensitive: is_sensitive,
                    validation_rule: None,
                    environment_override: None,
                    min_value: None,
                    max_value: None,
                    enum_values: None,
                    depends_on: vec![],
                    tags: vec![],
                    display_order: 0,
                    created_at: created_at.timestamp(),
                    modified_at: updated_at.timestamp(),
                }
            })
            .collect();

        Ok(Response::new(GetConfigurationResponse { settings }))
    }

    // -----------------------------------------------------------------------
    // UpdateConfiguration — upsert a key/value pair
    // -----------------------------------------------------------------------
    #[instrument(skip_all)]
    async fn update_configuration(
        &self,
        request: Request<UpdateConfigurationRequest>,
    ) -> Result<Response<UpdateConfigurationResponse>, Status> {
        let req = request.into_inner();

        if req.key.is_empty() {
            return Err(Status::invalid_argument("key is required"));
        }
        if req.category.is_empty() {
            return Err(Status::invalid_argument("category is required"));
        }

        // Upsert by key (which is UNIQUE in the table).  Also update
        // category / updated_by on conflict.
        sqlx::query(
            "INSERT INTO configuration (key, value, category, updated_by, updated_at) \
             VALUES ($1, $2, $3, $4, NOW()) \
             ON CONFLICT (key) DO UPDATE SET \
                 value = EXCLUDED.value, \
                 category = EXCLUDED.category, \
                 updated_by = EXCLUDED.updated_by, \
                 updated_at = NOW()",
        )
        .bind(&req.key)
        .bind(&req.value)
        .bind(&req.category)
        .bind(&req.changed_by)
        .execute(&self.pool)
        .await
        .map_err(|e| Status::internal(format!("DB error: {e}")))?;

        Ok(Response::new(UpdateConfigurationResponse {
            success: true,
            message: format!("Updated {}/{}", req.category, req.key),
            validation_result: None,
            timestamp: chrono::Utc::now().timestamp(),
        }))
    }

    // -----------------------------------------------------------------------
    // DeleteConfiguration
    // -----------------------------------------------------------------------
    #[instrument(skip_all)]
    async fn delete_configuration(
        &self,
        request: Request<DeleteConfigurationRequest>,
    ) -> Result<Response<DeleteConfigurationResponse>, Status> {
        let req = request.into_inner();
        sqlx::query("DELETE FROM configuration WHERE category = $1 AND key = $2")
            .bind(&req.category)
            .bind(&req.key)
            .execute(&self.pool)
            .await
            .map_err(|e| Status::internal(format!("DB error: {e}")))?;

        Ok(Response::new(DeleteConfigurationResponse {
            success: true,
            message: format!("Deleted {}/{}", req.category, req.key),
            timestamp: chrono::Utc::now().timestamp(),
        }))
    }

    // -----------------------------------------------------------------------
    // ListCategories — distinct categories from the table
    // -----------------------------------------------------------------------
    #[instrument(skip_all)]
    async fn list_categories(
        &self,
        _request: Request<ListCategoriesRequest>,
    ) -> Result<Response<ListCategoriesResponse>, Status> {
        let rows = sqlx::query(
            "SELECT DISTINCT category FROM configuration WHERE category IS NOT NULL ORDER BY category",
        )
        .fetch_all(&self.pool)
        .await
        .map_err(|e| Status::internal(format!("DB error: {e}")))?;

        let categories = rows
            .iter()
            .enumerate()
            .map(|(i, row)| {
                let name: String = row.get("category");
                ConfigurationCategory {
                    id: i as i64,
                    name: name.clone(),
                    description: name,
                    parent_id: None,
                    display_order: i as i32,
                    icon: None,
                    created_at: 0,
                    children: vec![],
                }
            })
            .collect();

        Ok(Response::new(ListCategoriesResponse { categories }))
    }

    // -----------------------------------------------------------------------
    // Remaining RPCs — not needed by the dashboard yet
    // -----------------------------------------------------------------------

    type StreamConfigChangesStream = std::pin::Pin<
        Box<dyn tokio_stream::Stream<Item = Result<ConfigChangeEvent, Status>> + Send>,
    >;

    async fn stream_config_changes(
        &self,
        _request: Request<StreamConfigChangesRequest>,
    ) -> Result<Response<Self::StreamConfigChangesStream>, Status> {
        Err(Status::unimplemented("StreamConfigChanges not yet implemented"))
    }

    async fn validate_configuration(
        &self,
        _request: Request<ValidateConfigurationRequest>,
    ) -> Result<Response<ValidateConfigurationResponse>, Status> {
        Err(Status::unimplemented("ValidateConfiguration not yet implemented"))
    }

    async fn get_configuration_history(
        &self,
        _request: Request<GetConfigurationHistoryRequest>,
    ) -> Result<Response<GetConfigurationHistoryResponse>, Status> {
        Err(Status::unimplemented("GetConfigurationHistory not yet implemented"))
    }

    async fn rollback_configuration(
        &self,
        _request: Request<RollbackConfigurationRequest>,
    ) -> Result<Response<RollbackConfigurationResponse>, Status> {
        Err(Status::unimplemented("RollbackConfiguration not yet implemented"))
    }

    async fn export_configuration(
        &self,
        _request: Request<ExportConfigurationRequest>,
    ) -> Result<Response<ExportConfigurationResponse>, Status> {
        Err(Status::unimplemented("ExportConfiguration not yet implemented"))
    }

    async fn import_configuration(
        &self,
        _request: Request<ImportConfigurationRequest>,
    ) -> Result<Response<ImportConfigurationResponse>, Status> {
        Err(Status::unimplemented("ImportConfiguration not yet implemented"))
    }

    async fn get_config_schema(
        &self,
        _request: Request<GetConfigSchemaRequest>,
    ) -> Result<Response<GetConfigSchemaResponse>, Status> {
        Err(Status::unimplemented("GetConfigSchema not yet implemented"))
    }

    async fn update_config_schema(
        &self,
        _request: Request<UpdateConfigSchemaRequest>,
    ) -> Result<Response<UpdateConfigSchemaResponse>, Status> {
        Err(Status::unimplemented("UpdateConfigSchema not yet implemented"))
    }
}
