---
to: "generated/src/database/{{ name | snake }}.rs"
vars:
  name: "{{ name }}"
  database: "{{ database }}"
  orm: "{{ orm }}"
prefixes:
  ex: "http://example.org/advanced-rust-project/"
base: "http://example.org/advanced-rust-project/"
rdf:
  - "data/domain.ttl"
sparql:
  find_tables: "SELECT ?table WHERE { ?table a ex:Table }"
  find_columns: "SELECT ?column WHERE { ?column a ex:Column }"
  find_relationships: "SELECT ?rel WHERE { ?rel a ex:Relationship }"
---

//! {{ name | title }} Database Schema
//! 
//! Database: {{ database }}
//! ORM: {{ orm }}
//! 
//! Generated by ggen on {{ "now" | date(format="%Y-%m-%d %H:%M:%S") }}

use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Row};
use chrono::{DateTime, Utc};
use anyhow::Result;

/// {{ name | title }} Database Models
{% for table in sparql_results.find_tables %}
{% set table_name = sparql_first(results=sparql_results.find_tables, column="table") %}
{% set table_label = table_name | local %}
{% set table_snake = table_label | snake %}
{% set table_pascal = table_label | pascal %}

#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct {{ table_pascal }} {
    {% for column in sparql_results.find_columns %}
    {% if column.table == table_name %}
    {% set column_name = column.column | local %}
    {% set column_snake = column_name | snake %}
    {% set column_pascal = column_name | pascal %}
    {% set data_type = column.dataType %}
    {% if data_type == "VARCHAR(255)" %}
    pub {{ column_snake }}: String,
    {% elif data_type == "TEXT" %}
    pub {{ column_snake }}: Option<String>,
    {% elif data_type == "DECIMAL(10,2)" %}
    pub {{ column_snake }}: rust_decimal::Decimal,
    {% elif data_type == "TIMESTAMP" %}
    pub {{ column_snake }}: DateTime<Utc>,
    {% else %}
    pub {{ column_snake }}: String,
    {% endif %}
    {% endif %}
    {% endfor %}
}

impl {{ table_pascal }} {
    /// Create new {{ table_label }} instance
    pub fn new(
        {% for column in sparql_results.find_columns %}
        {% if column.table == table_name and column.isPrimaryKey != "true" %}
        {% set column_name = column.column | local %}
        {% set column_snake = column_name | snake %}
        {% set data_type = column.dataType %}
        {% if data_type == "VARCHAR(255)" %}
        {{ column_snake }}: String,
        {% elif data_type == "TEXT" %}
        {{ column_snake }}: Option<String>,
        {% elif data_type == "DECIMAL(10,2)" %}
        {{ column_snake }}: rust_decimal::Decimal,
        {% elif data_type == "TIMESTAMP" %}
        {{ column_snake }}: DateTime<Utc>,
        {% else %}
        {{ column_snake }}: String,
        {% endif %}
        {% endif %}
        {% endfor %}
    ) -> Self {
        Self {
            {% for column in sparql_results.find_columns %}
            {% if column.table == table_name %}
            {% set column_name = column.column | local %}
            {% set column_snake = column_name | snake %}
            {% set data_type = column.dataType %}
            {% if data_type == "TIMESTAMP" and column.defaultValue == "CURRENT_TIMESTAMP" %}
            {{ column_snake }}: Utc::now(),
            {% else %}
            {{ column_snake }},
            {% endif %}
            {% endif %}
            {% endfor %}
        }
    }

    /// Get table name
    pub fn table_name() -> &'static str {
        "{{ table_snake }}"
    }

    /// Get primary key column
    pub fn primary_key() -> &'static str {
        {% for column in sparql_results.find_columns %}
        {% if column.table == table_name and column.isPrimaryKey == "true" %}
        "{{ column.columnName }}"
        {% endif %}
        {% endfor %}
    }
}

{% endfor %}

/// {{ name | title }} Database Repository
pub struct {{ name | pascal }}Repository {
    pool: sqlx::PgPool,
}

impl {{ name | pascal }}Repository {
    /// Create new repository instance
    pub fn new(pool: sqlx::PgPool) -> Self {
        Self { pool }
    }

    /// Initialize database schema
    pub async fn init_schema(&self) -> Result<()> {
        // Create tables
        {% for table in sparql_results.find_tables %}
        {% set table_name = sparql_first(results=sparql_results.find_tables, column="table") %}
        {% set table_label = table_name | local %}
        {% set table_snake = table_label | snake %}
        {% set table_pascal = table_label | pascal %}
        
        sqlx::query(&format!(r#"
            CREATE TABLE IF NOT EXISTS {} (
                {% for column in sparql_results.find_columns %}
                {% if column.table == table_name %}
                {} {} {% if column.isNotNull == "true" %}NOT NULL{% endif %} {% if column.isPrimaryKey == "true" %}PRIMARY KEY{% endif %} {% if column.defaultValue %}DEFAULT {} {% endif %},
                {% endif %}
                {% endfor %}
            )
        "#, "{{ table_snake }}", 
            {% for column in sparql_results.find_columns %}
            {% if column.table == table_name %}
            "{{ column.columnName }}", "{{ column.dataType }}", "{{ column.defaultValue }}"
            {% endif %}
            {% endfor %}
        ))
        .execute(&self.pool)
        .await?;
        {% endfor %}

        Ok(())
    }

    /// Get all records
    pub async fn find_all<T>(&self) -> Result<Vec<T>>
    where
        T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let query = format!("SELECT * FROM {}", T::table_name());
        let rows = sqlx::query_as::<_, T>(&query)
            .fetch_all(&self.pool)
            .await?;
        Ok(rows)
    }

    /// Find by ID
    pub async fn find_by_id<T>(&self, id: &str) -> Result<Option<T>>
    where
        T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let query = format!("SELECT * FROM {} WHERE {} = $1", T::table_name(), T::primary_key());
        let row = sqlx::query_as::<_, T>(&query)
            .bind(id)
            .fetch_optional(&self.pool)
            .await?;
        Ok(row)
    }

    /// Create new record
    pub async fn create<T>(&self, record: &T) -> Result<T>
    where
        T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin + Clone,
    {
        // TODO: Implement generic create method
        Ok(record.clone())
    }

    /// Update existing record
    pub async fn update<T>(&self, id: &str, record: &T) -> Result<Option<T>>
    where
        T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        // TODO: Implement generic update method
        Ok(None)
    }

    /// Delete record
    pub async fn delete<T>(&self, id: &str) -> Result<bool>
    where
        T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
    {
        let query = format!("DELETE FROM {} WHERE {} = $1", T::table_name(), T::primary_key());
        let result = sqlx::query(&query)
            .bind(id)
            .execute(&self.pool)
            .await?;
        Ok(result.rows_affected() > 0)
    }
}

/// {{ name | title }} Database Migrations
pub struct {{ name | pascal }}Migrations;

impl {{ name | pascal }}Migrations {
    /// Run all migrations
    pub async fn run_all(pool: &sqlx::PgPool) -> Result<()> {
        // Create migration table
        sqlx::query(r#"
            CREATE TABLE IF NOT EXISTS schema_migrations (
                version VARCHAR(255) PRIMARY KEY,
                applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        "#)
        .execute(pool)
        .await?;

        // Run migrations
        Self::migration_001_initial_schema(pool).await?;
        Self::migration_002_add_indexes(pool).await?;
        Self::migration_003_add_constraints(pool).await?;

        Ok(())
    }

    /// Initial schema migration
    async fn migration_001_initial_schema(pool: &sqlx::PgPool) -> Result<()> {
        let version = "001_initial_schema";
        
        // Check if migration already applied
        let exists = sqlx::query_scalar::<_, bool>(
            "SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = $1)"
        )
        .bind(version)
        .fetch_one(pool)
        .await?;

        if exists {
            return Ok(());
        }

        // Apply migration
        {% for table in sparql_results.find_tables %}
        {% set table_name = sparql_first(results=sparql_results.find_tables, column="table") %}
        {% set table_label = table_name | local %}
        {% set table_snake = table_label | snake %}
        
        sqlx::query(&format!(r#"
            CREATE TABLE IF NOT EXISTS {} (
                {% for column in sparql_results.find_columns %}
                {% if column.table == table_name %}
                {} {} {% if column.isNotNull == "true" %}NOT NULL{% endif %} {% if column.isPrimaryKey == "true" %}PRIMARY KEY{% endif %} {% if column.defaultValue %}DEFAULT {} {% endif %},
                {% endif %}
                {% endfor %}
            )
        "#, "{{ table_snake }}",
            {% for column in sparql_results.find_columns %}
            {% if column.table == table_name %}
            "{{ column.columnName }}", "{{ column.dataType }}", "{{ column.defaultValue }}"
            {% endif %}
            {% endfor %}
        ))
        .execute(pool)
        .await?;
        {% endfor %}

        // Record migration
        sqlx::query("INSERT INTO schema_migrations (version) VALUES ($1)")
            .bind(version)
            .execute(pool)
            .await?;

        Ok(())
    }

    /// Add indexes migration
    async fn migration_002_add_indexes(pool: &sqlx::PgPool) -> Result<()> {
        let version = "002_add_indexes";
        
        // Check if migration already applied
        let exists = sqlx::query_scalar::<_, bool>(
            "SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = $1)"
        )
        .bind(version)
        .fetch_one(pool)
        .await?;

        if exists {
            return Ok(());
        }

        // Add indexes
        {% for column in sparql_results.find_columns %}
        {% if column.isUnique == "true" %}
        sqlx::query(&format!("CREATE UNIQUE INDEX IF NOT EXISTS idx_{}_{} ON {} ({})", 
            "{{ column.table | local | snake }}", 
            "{{ column.columnName }}", 
            "{{ column.table | local | snake }}", 
            "{{ column.columnName }}"
        ))
        .execute(pool)
        .await?;
        {% endif %}
        {% endfor %}

        // Record migration
        sqlx::query("INSERT INTO schema_migrations (version) VALUES ($1)")
            .bind(version)
            .execute(pool)
            .await?;

        Ok(())
    }

    /// Add constraints migration
    async fn migration_003_add_constraints(pool: &sqlx::PgPool) -> Result<()> {
        let version = "003_add_constraints";
        
        // Check if migration already applied
        let exists = sqlx::query_scalar::<_, bool>(
            "SELECT EXISTS(SELECT 1 FROM schema_migrations WHERE version = $1)"
        )
        .bind(version)
        .fetch_one(pool)
        .await?;

        if exists {
            return Ok(());
        }

        // Add foreign key constraints
        {% for column in sparql_results.find_columns %}
        {% if column.isForeignKey %}
        sqlx::query(&format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {}({})", 
            "{{ column.table | local | snake }}", 
            "{{ column.table | local | snake }}", 
            "{{ column.columnName }}", 
            "{{ column.columnName }}", 
            "{{ column.isForeignKey | local | snake }}", 
            "{{ column.isForeignKey | local | snake }}_id"
        ))
        .execute(pool)
        .await?;
        {% endif %}
        {% endfor %}

        // Record migration
        sqlx::query("INSERT INTO schema_migrations (version) VALUES ($1)")
            .bind(version)
            .execute(pool)
            .await?;

        Ok(())
    }
}

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

    #[tokio::test]
    async fn test_repository_operations() {
        // TODO: Set up test database
        let pool = PgPool::connect("postgresql://localhost/test_db").await.unwrap();
        let repo = {{ name | pascal }}Repository::new(pool);

        // Test schema initialization
        assert!(repo.init_schema().await.is_ok());
    }

    #[tokio::test]
    async fn test_migrations() {
        // TODO: Set up test database
        let pool = PgPool::connect("postgresql://localhost/test_db").await.unwrap();

        // Test migrations
        assert!({{ name | pascal }}Migrations::run_all(&pool).await.is_ok());
    }
}
