---
to: generated/src/services/{{name}}.rs
vars:
  name: "user-service"
  description: "User management service"
  version: "1.0.0"
  author: "ggen-examples"
sparql:
  find_entities: "SELECT ?entity ?label WHERE { ?entity a ex:Entity ; rdfs:label ?label }"
  find_properties: "SELECT ?entity ?property ?label WHERE { ?entity ex:hasProperty ?property . ?property rdfs:label ?label }"
  find_relationships: "SELECT ?entity ?relationship ?label WHERE { ?entity ex:hasRelationship ?relationship . ?relationship rdfs:label ?label }"
freeze_policy: "checksum"
freeze_slots_dir: "generated/.ggen/freeze"
---

//! {{description}}
//! 
//! This service provides comprehensive {{name}} functionality including:
//! - CRUD operations for {{name}} entities
//! - Business logic validation
//! - Integration with other services
//! - Comprehensive error handling
//! - Performance optimization
//! - Security features
//!
//! Generated by ggen on {{ "now" | date(format="%Y-%m-%d %H:%M:%S") }}

use anyhow::{Context, Result};
use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::Json,
    routing::{get, post, put, delete},
    Router,
};
use serde::{Deserialize, Serialize};
use sqlx::{PgPool, Row};
use std::collections::HashMap;
use tokio::time::{timeout, Duration};
use tracing::{info, warn, error};
use uuid::Uuid;

/// Configuration for the {{name}} service
#[derive(Debug, Clone)]
pub struct {{name | title}}ServiceConfig {
    pub database_url: String,
    pub max_connections: u32,
    pub timeout_seconds: u64,
    pub enable_caching: bool,
    pub cache_ttl_seconds: u64,
}

impl Default for {{name | title}}ServiceConfig {
    fn default() -> Self {
        Self {
            database_url: "postgresql://localhost/comprehensive_showcase".to_string(),
            max_connections: 10,
            timeout_seconds: 30,
            enable_caching: true,
            cache_ttl_seconds: 300,
        }
    }
}

/// Main service struct
#[derive(Debug, Clone)]
pub struct {{name | title}}Service {
    config: {{name | title}}ServiceConfig,
    db_pool: PgPool,
    cache: Option<HashMap<String, String>>,
}

impl {{name | title}}Service {
    /// Create a new service instance
    pub async fn new(config: {{name | title}}ServiceConfig) -> Result<Self> {
        let db_pool = PgPool::connect(&config.database_url)
            .await
            .context("Failed to connect to database")?;

        let cache = if config.enable_caching {
            Some(HashMap::new())
        } else {
            None
        };

        Ok(Self {
            config,
            db_pool,
            cache,
        })
    }

    /// Get the database pool
    pub fn db_pool(&self) -> &PgPool {
        &self.db_pool
    }

    /// Get the configuration
    pub fn config(&self) -> &{{name | title}}ServiceConfig {
        &self.config
    }
}

/// {{name | title}} entity
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct {{name | title}} {
    pub id: Uuid,
    pub name: String,
    pub email: String,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

/// Create {{name}} request
#[derive(Debug, Deserialize)]
pub struct Create{{name | title}}Request {
    pub name: String,
    pub email: String,
}

/// Update {{name}} request
#[derive(Debug, Deserialize)]
pub struct Update{{name | title}}Request {
    pub name: Option<String>,
    pub email: Option<String>,
}

/// {{name | title}} query parameters
#[derive(Debug, Deserialize)]
pub struct {{name | title}}Query {
    pub page: Option<u32>,
    pub limit: Option<u32>,
    pub sort: Option<String>,
    pub search: Option<String>,
}

/// Response wrapper
#[derive(Debug, Serialize)]
pub struct {{name | title}}Response<T> {
    pub data: T,
    pub message: String,
    pub timestamp: chrono::DateTime<chrono::Utc>,
}

/// Paginated response
#[derive(Debug, Serialize)]
pub struct Paginated{{name | title}}Response {
    pub items: Vec<{{name | title}}>,
    pub total: u64,
    pub page: u32,
    pub limit: u32,
    pub pages: u32,
}

/// Service implementation
impl {{name | title}}Service {
    /// Create a new {{name}}
    pub async fn create_{{name}}(
        &self,
        request: Create{{name | title}}Request,
    ) -> Result<{{name | title}}> {
        info!("Creating new {{name}} with email: {}", request.email);

        // Validate input
        self.validate_create_request(&request)?;

        // Check if email already exists
        if self.email_exists(&request.email).await? {
            return Err(anyhow::anyhow!("Email already exists"));
        }

        // Create {{name}} in database
        let {{name}} = sqlx::query_as!(
            {{name | title}},
            r#"
            INSERT INTO users (name, email)
            VALUES ($1, $2)
            RETURNING id, name, email, created_at, updated_at
            "#,
            request.name,
            request.email
        )
        .fetch_one(&self.db_pool)
        .await
        .context("Failed to create {{name}}")?;

        info!("Created {{name}} with ID: {}", {{name}}.id);
        Ok({{name}})
    }

    /// Get {{name}} by ID
    pub async fn get_{{name}}(&self, id: Uuid) -> Result<Option<{{name | title}}>> {
        info!("Getting {{name}} with ID: {}", id);

        let {{name}} = sqlx::query_as!(
            {{name | title}},
            r#"
            SELECT id, name, email, created_at, updated_at
            FROM users
            WHERE id = $1
            "#,
            id
        )
        .fetch_optional(&self.db_pool)
        .await
        .context("Failed to get {{name}}")?;

        Ok({{name}})
    }

    /// Get all {{name}}s with pagination
    pub async fn get_{{name}}s(
        &self,
        query: {{name | title}}Query,
    ) -> Result<Paginated{{name | title}}Response> {
        info!("Getting {{name}}s with query: {:?}", query);

        let page = query.page.unwrap_or(1);
        let limit = query.limit.unwrap_or(20).min(100);
        let offset = (page - 1) * limit;

        // Build query
        let mut sql = "SELECT id, name, email, created_at, updated_at FROM users".to_string();
        let mut count_sql = "SELECT COUNT(*) FROM users".to_string();
        let mut conditions = Vec::new();
        let mut params: Vec<Box<dyn sqlx::Encode<'_, sqlx::Postgres> + Send + Sync>> = Vec::new();

        if let Some(search) = &query.search {
            conditions.push("(name ILIKE $1 OR email ILIKE $1)".to_string());
            params.push(Box::new(format!("%{}%", search)));
        }

        if !conditions.is_empty() {
            let where_clause = format!(" WHERE {}", conditions.join(" AND "));
            sql.push_str(&where_clause);
            count_sql.push_str(&where_clause);
        }

        // Add sorting
        let sort_field = query.sort.unwrap_or_else(|| "created_at".to_string());
        sql.push_str(&format!(" ORDER BY {} DESC", sort_field));

        // Add pagination
        sql.push_str(&format!(" LIMIT {} OFFSET {}", limit, offset));

        // Execute queries
        let {{name}}s = sqlx::query_as::<_, {{name | title}}>(&sql)
            .fetch_all(&self.db_pool)
            .await
            .context("Failed to get {{name}}s")?;

        let total: i64 = sqlx::query_scalar(&count_sql)
            .fetch_one(&self.db_pool)
            .await
            .context("Failed to get {{name}} count")?;

        let pages = ((total as f64) / (limit as f64)).ceil() as u32;

        Ok(Paginated{{name | title}}Response {
            items: {{name}}s,
            total: total as u64,
            page,
            limit,
            pages,
        })
    }

    /// Update {{name}}
    pub async fn update_{{name}}(
        &self,
        id: Uuid,
        request: Update{{name | title}}Request,
    ) -> Result<{{name | title}}> {
        info!("Updating {{name}} with ID: {}", id);

        // Validate input
        self.validate_update_request(&request)?;

        // Check if {{name}} exists
        if self.get_{{name}}(id).await?.is_none() {
            return Err(anyhow::anyhow!("{{name | title}} not found"));
        }

        // Check email uniqueness if email is being updated
        if let Some(ref email) = request.email {
            if self.email_exists(email).await? {
                return Err(anyhow::anyhow!("Email already exists"));
            }
        }

        // Build update query
        let mut set_clauses = Vec::new();
        let mut params: Vec<Box<dyn sqlx::Encode<'_, sqlx::Postgres> + Send + Sync>> = Vec::new();
        let mut param_count = 1;

        if let Some(ref name) = request.name {
            set_clauses.push(format!("name = ${}", param_count));
            params.push(Box::new(name.clone()));
            param_count += 1;
        }

        if let Some(ref email) = request.email {
            set_clauses.push(format!("email = ${}", param_count));
            params.push(Box::new(email.clone()));
            param_count += 1;
        }

        set_clauses.push(format!("updated_at = CURRENT_TIMESTAMP"));

        let sql = format!(
            "UPDATE users SET {} WHERE id = ${} RETURNING id, name, email, created_at, updated_at",
            set_clauses.join(", "),
            param_count
        );

        let {{name}} = sqlx::query_as::<_, {{name | title}}>(&sql)
            .bind(id)
            .fetch_one(&self.db_pool)
            .await
            .context("Failed to update {{name}}")?;

        info!("Updated {{name}} with ID: {}", {{name}}.id);
        Ok({{name}})
    }

    /// Delete {{name}}
    pub async fn delete_{{name}}(&self, id: Uuid) -> Result<()> {
        info!("Deleting {{name}} with ID: {}", id);

        let result = sqlx::query!(
            "DELETE FROM users WHERE id = $1",
            id
        )
        .execute(&self.db_pool)
        .await
        .context("Failed to delete {{name}}")?;

        if result.rows_affected() == 0 {
            return Err(anyhow::anyhow!("{{name | title}} not found"));
        }

        info!("Deleted {{name}} with ID: {}", id);
        Ok(())
    }

    /// Check if email exists
    async fn email_exists(&self, email: &str) -> Result<bool> {
        let count: i64 = sqlx::query_scalar!(
            "SELECT COUNT(*) FROM users WHERE email = $1",
            email
        )
        .fetch_one(&self.db_pool)
        .await
        .context("Failed to check email existence")?;

        Ok(count > 0)
    }

    /// Validate create request
    fn validate_create_request(&self, request: &Create{{name | title}}Request) -> Result<()> {
        if request.name.trim().is_empty() {
            return Err(anyhow::anyhow!("Name cannot be empty"));
        }

        if request.name.len() > 100 {
            return Err(anyhow::anyhow!("Name too long"));
        }

        if !self.is_valid_email(&request.email) {
            return Err(anyhow::anyhow!("Invalid email format"));
        }

        Ok(())
    }

    /// Validate update request
    fn validate_update_request(&self, request: &Update{{name | title}}Request) -> Result<()> {
        if let Some(ref name) = request.name {
            if name.trim().is_empty() {
                return Err(anyhow::anyhow!("Name cannot be empty"));
            }

            if name.len() > 100 {
                return Err(anyhow::anyhow!("Name too long"));
            }
        }

        if let Some(ref email) = request.email {
            if !self.is_valid_email(email) {
                return Err(anyhow::anyhow!("Invalid email format"));
            }
        }

        Ok(())
    }

    /// Validate email format
    fn is_valid_email(&self, email: &str) -> bool {
        email.contains('@') && email.len() > 5 && email.len() < 255
    }
}

/// HTTP handlers
pub async fn create_{{name}}_handler(
    State(service): State<{{name | title}}Service>,
    Json(request): Json<Create{{name | title}}Request>,
) -> Result<Json<{{name | title}}Response<{{name | title}}>>, StatusCode> {
    match timeout(
        Duration::from_secs(service.config().timeout_seconds),
        service.create_{{name}}(request),
    )
    .await
    {
        Ok(Ok({{name}})) => Ok(Json({{name | title}}Response {
            data: {{name}},
            message: "{{name | title}} created successfully".to_string(),
            timestamp: chrono::Utc::now(),
        })),
        Ok(Err(e)) => {
            error!("Failed to create {{name}}: {}", e);
            Err(StatusCode::BAD_REQUEST)
        }
        Err(_) => {
            error!("Timeout creating {{name}}");
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

pub async fn get_{{name}}_handler(
    State(service): State<{{name | title}}Service>,
    Path(id): Path<Uuid>,
) -> Result<Json<{{name | title}}Response<{{name | title}}>>, StatusCode> {
    match timeout(
        Duration::from_secs(service.config().timeout_seconds),
        service.get_{{name}}(id),
    )
    .await
    {
        Ok(Ok(Some({{name}}))) => Ok(Json({{name | title}}Response {
            data: {{name}},
            message: "{{name | title}} retrieved successfully".to_string(),
            timestamp: chrono::Utc::now(),
        })),
        Ok(Ok(None)) => Err(StatusCode::NOT_FOUND),
        Ok(Err(e)) => {
            error!("Failed to get {{name}}: {}", e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
        Err(_) => {
            error!("Timeout getting {{name}}");
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

pub async fn get_{{name}}s_handler(
    State(service): State<{{name | title}}Service>,
    Query(query): Query<{{name | title}}Query>,
) -> Result<Json<{{name | title}}Response<Paginated{{name | title}}Response>>, StatusCode> {
    match timeout(
        Duration::from_secs(service.config().timeout_seconds),
        service.get_{{name}}s(query),
    )
    .await
    {
        Ok(Ok(response)) => Ok(Json({{name | title}}Response {
            data: response,
            message: "{{name | title}}s retrieved successfully".to_string(),
            timestamp: chrono::Utc::now(),
        })),
        Ok(Err(e)) => {
            error!("Failed to get {{name}}s: {}", e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
        Err(_) => {
            error!("Timeout getting {{name}}s");
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

pub async fn update_{{name}}_handler(
    State(service): State<{{name | title}}Service>,
    Path(id): Path<Uuid>,
    Json(request): Json<Update{{name | title}}Request>,
) -> Result<Json<{{name | title}}Response<{{name | title}}>>, StatusCode> {
    match timeout(
        Duration::from_secs(service.config().timeout_seconds),
        service.update_{{name}}(id, request),
    )
    .await
    {
        Ok(Ok({{name}})) => Ok(Json({{name | title}}Response {
            data: {{name}},
            message: "{{name | title}} updated successfully".to_string(),
            timestamp: chrono::Utc::now(),
        })),
        Ok(Err(e)) => {
            error!("Failed to update {{name}}: {}", e);
            Err(StatusCode::BAD_REQUEST)
        }
        Err(_) => {
            error!("Timeout updating {{name}}");
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

pub async fn delete_{{name}}_handler(
    State(service): State<{{name | title}}Service>,
    Path(id): Path<Uuid>,
) -> Result<Json<{{name | title}}Response<()>>, StatusCode> {
    match timeout(
        Duration::from_secs(service.config().timeout_seconds),
        service.delete_{{name}}(id),
    )
    .await
    {
        Ok(Ok(())) => Ok(Json({{name | title}}Response {
            data: (),
            message: "{{name | title}} deleted successfully".to_string(),
            timestamp: chrono::Utc::now(),
        })),
        Ok(Err(e)) => {
            error!("Failed to delete {{name}}: {}", e);
            Err(StatusCode::NOT_FOUND)
        }
        Err(_) => {
            error!("Timeout deleting {{name}}");
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

/// Create the router
pub fn create_router(service: {{name | title}}Service) -> Router {
    Router::new()
        .route("/{{name}}s", post(create_{{name}}_handler))
        .route("/{{name}}s", get(get_{{name}}s_handler))
        .route("/{{name}}s/:id", get(get_{{name}}_handler))
        .route("/{{name}}s/:id", put(update_{{name}}_handler))
        .route("/{{name}}s/:id", delete(delete_{{name}}_handler))
        .with_state(service)
}

#[cfg(test)]
mod tests {
    use super::*;
    use sqlx::PgPool;
    use tempfile::TempDir;

    async fn setup_test_db() -> PgPool {
        // Setup test database
        let database_url = "postgresql://localhost/comprehensive_showcase_test";
        PgPool::connect(database_url).await.unwrap()
    }

    #[tokio::test]
    async fn test_create_{{name}}() {
        let db_pool = setup_test_db().await;
        let config = {{name | title}}ServiceConfig::default();
        let service = {{name | title}}Service::new(config).await.unwrap();

        let request = Create{{name | title}}Request {
            name: "Test User".to_string(),
            email: "test@example.com".to_string(),
        };

        let result = service.create_{{name}}(request).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_get_{{name}}() {
        let db_pool = setup_test_db().await;
        let config = {{name | title}}ServiceConfig::default();
        let service = {{name | title}}Service::new(config).await.unwrap();

        let id = Uuid::new_v4();
        let result = service.get_{{name}}(id).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_update_{{name}}() {
        let db_pool = setup_test_db().await;
        let config = {{name | title}}ServiceConfig::default();
        let service = {{name | title}}Service::new(config).await.unwrap();

        let id = Uuid::new_v4();
        let request = Update{{name | title}}Request {
            name: Some("Updated Name".to_string()),
            email: None,
        };

        let result = service.update_{{name}}(id, request).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_delete_{{name}}() {
        let db_pool = setup_test_db().await;
        let config = {{name | title}}ServiceConfig::default();
        let service = {{name | title}}Service::new(config).await.unwrap();

        let id = Uuid::new_v4();
        let result = service.delete_{{name}}(id).await;
        assert!(result.is_ok());
    }
}
