//! Repository layer for database operations
//!
//! This module provides the repository pattern for all database entities in Attune.
//! Repositories abstract database operations and provide a clean interface for CRUD
//! operations and queries.
//!
//! # Architecture
//!
//! - Each entity has its own repository module (e.g., `pack`, `action`, `trigger`)
//! - Repositories use SQLx for database operations
//! - Transaction support is provided through SQLx's transaction types
//! - All operations return `Result<T, Error>` for consistent error handling
//!
//! # Example
//!
//! ```rust,no_run
//! use attune_common::repositories::{PackRepository, FindByRef};
//! use attune_common::db::Database;
//!
//! async fn example(db: &Database) -> attune_common::Result<()> {
//!     if let Some(pack) = PackRepository::find_by_ref(db.pool(), "core").await? {
//!         println!("Found pack: {}", pack.label);
//!     }
//!     Ok(())
//! }
//! ```

use sqlx::{Executor, Postgres, Transaction};

pub mod action;
pub mod analytics;
pub mod artifact;
pub mod entity_history;
pub mod event;
pub mod execution;
pub mod execution_admission;
pub mod identity;
pub mod inquiry;
pub mod key;
pub mod notification;
pub mod pack;
pub mod pack_test;
pub mod queue_stats;
pub mod rule;
pub mod runtime;
pub mod runtime_version;
pub mod trigger;
pub mod workflow;

// Re-export repository types
pub use action::{ActionRepository, PolicyRepository};
pub use analytics::AnalyticsRepository;
pub use artifact::{ArtifactRepository, ArtifactVersionRepository};
pub use entity_history::EntityHistoryRepository;
pub use event::{EnforcementRepository, EventRepository};
pub use execution::ExecutionRepository;
pub use execution_admission::ExecutionAdmissionRepository;
pub use identity::{IdentityRepository, PermissionAssignmentRepository, PermissionSetRepository};
pub use inquiry::InquiryRepository;
pub use key::KeyRepository;
pub use notification::NotificationRepository;
pub use pack::PackRepository;
pub use pack_test::PackTestRepository;
pub use queue_stats::QueueStatsRepository;
pub use rule::RuleRepository;
pub use runtime::{RuntimeRepository, WorkerRepository};
pub use runtime_version::RuntimeVersionRepository;
pub use trigger::{SensorRepository, TriggerRepository};
pub use workflow::{WorkflowDefinitionRepository, WorkflowExecutionRepository};

/// Explicit patch operation for update inputs where callers must distinguish
/// between "leave unchanged", "set value", and "clear to NULL".
#[derive(Debug, Clone, PartialEq)]
pub enum Patch<T> {
    Set(T),
    Clear,
}

/// Type alias for database connection/transaction
pub type DbConnection<'c> = &'c mut Transaction<'c, Postgres>;

/// Base repository trait providing common functionality
///
/// This trait is not meant to be used directly, but serves as a foundation
/// for specific repository implementations.
pub trait Repository {
    /// The entity type this repository manages
    type Entity;

    /// Get the name of the table for this repository
    fn table_name() -> &'static str;
}

/// Trait for repositories that support finding by ID
#[async_trait::async_trait]
pub trait FindById: Repository {
    /// Find an entity by its ID
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    /// * `id` - The ID to search for
    ///
    /// # Returns
    ///
    /// * `Ok(Some(entity))` if found
    /// * `Ok(None)` if not found
    /// * `Err(error)` on database error
    async fn find_by_id<'e, E>(executor: E, id: i64) -> crate::Result<Option<Self::Entity>>
    where
        E: Executor<'e, Database = Postgres> + 'e;

    /// Get an entity by its ID, returning an error if not found
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    /// * `id` - The ID to search for
    ///
    /// # Returns
    ///
    /// * `Ok(entity)` if found
    /// * `Err(NotFound)` if not found
    /// * `Err(error)` on database error
    async fn get_by_id<'e, E>(executor: E, id: i64) -> crate::Result<Self::Entity>
    where
        E: Executor<'e, Database = Postgres> + 'e,
    {
        Self::find_by_id(executor, id)
            .await?
            .ok_or_else(|| crate::Error::not_found(Self::table_name(), "id", id.to_string()))
    }
}

/// Trait for repositories that support finding by reference
#[async_trait::async_trait]
pub trait FindByRef: Repository {
    /// Find an entity by its reference string
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    /// * `ref_str` - The reference string to search for
    ///
    /// # Returns
    ///
    /// * `Ok(Some(entity))` if found
    /// * `Ok(None)` if not found
    /// * `Err(error)` on database error
    async fn find_by_ref<'e, E>(executor: E, ref_str: &str) -> crate::Result<Option<Self::Entity>>
    where
        E: Executor<'e, Database = Postgres> + 'e;

    /// Get an entity by its reference, returning an error if not found
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    /// * `ref_str` - The reference string to search for
    ///
    /// # Returns
    ///
    /// * `Ok(entity)` if found
    /// * `Err(NotFound)` if not found
    /// * `Err(error)` on database error
    async fn get_by_ref<'e, E>(executor: E, ref_str: &str) -> crate::Result<Self::Entity>
    where
        E: Executor<'e, Database = Postgres> + 'e,
    {
        Self::find_by_ref(executor, ref_str)
            .await?
            .ok_or_else(|| crate::Error::not_found(Self::table_name(), "ref", ref_str))
    }
}

/// Trait for repositories that support listing all entities
#[async_trait::async_trait]
pub trait List: Repository {
    /// List all entities
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    ///
    /// # Returns
    ///
    /// * `Ok(Vec<entity>)` - List of all entities
    /// * `Err(error)` on database error
    async fn list<'e, E>(executor: E) -> crate::Result<Vec<Self::Entity>>
    where
        E: Executor<'e, Database = Postgres> + 'e;
}

/// Trait for repositories that support creating entities
#[async_trait::async_trait]
pub trait Create: Repository {
    /// Input type for creating a new entity
    type CreateInput;

    /// Create a new entity
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    /// * `input` - The data for creating the entity
    ///
    /// # Returns
    ///
    /// * `Ok(entity)` - The created entity
    /// * `Err(error)` on database error or validation failure
    async fn create<'e, E>(executor: E, input: Self::CreateInput) -> crate::Result<Self::Entity>
    where
        E: Executor<'e, Database = Postgres> + 'e;
}

/// Trait for repositories that support updating entities
#[async_trait::async_trait]
pub trait Update: Repository {
    /// Input type for updating an entity
    type UpdateInput;

    /// Update an existing entity by ID
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    /// * `id` - The ID of the entity to update
    /// * `input` - The data for updating the entity
    ///
    /// # Returns
    ///
    /// * `Ok(entity)` - The updated entity
    /// * `Err(NotFound)` if the entity doesn't exist
    /// * `Err(error)` on database error or validation failure
    async fn update<'e, E>(
        executor: E,
        id: i64,
        input: Self::UpdateInput,
    ) -> crate::Result<Self::Entity>
    where
        E: Executor<'e, Database = Postgres> + 'e;
}

/// Trait for repositories that support deleting entities
#[async_trait::async_trait]
pub trait Delete: Repository {
    /// Delete an entity by ID
    ///
    /// # Arguments
    ///
    /// * `executor` - Database executor (pool or transaction)
    /// * `id` - The ID of the entity to delete
    ///
    /// # Returns
    ///
    /// * `Ok(true)` if the entity was deleted
    /// * `Ok(false)` if the entity didn't exist
    /// * `Err(error)` on database error
    async fn delete<'e, E>(executor: E, id: i64) -> crate::Result<bool>
    where
        E: Executor<'e, Database = Postgres> + 'e;
}

/// Helper struct for pagination parameters
#[derive(Debug, Clone, Copy)]
pub struct Pagination {
    /// Page number (0-based)
    pub page: i64,
    /// Number of items per page
    pub per_page: i64,
}

impl Pagination {
    /// Create a new Pagination instance
    pub fn new(page: i64, per_page: i64) -> Self {
        Self { page, per_page }
    }

    /// Calculate the OFFSET for SQL queries
    pub fn offset(&self) -> i64 {
        self.page * self.per_page
    }

    /// Get the LIMIT for SQL queries
    pub fn limit(&self) -> i64 {
        self.per_page
    }
}

impl Default for Pagination {
    fn default() -> Self {
        Self {
            page: 0,
            per_page: 50,
        }
    }
}

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

    #[test]
    fn test_pagination() {
        let p = Pagination::new(0, 10);
        assert_eq!(p.offset(), 0);
        assert_eq!(p.limit(), 10);

        let p = Pagination::new(2, 10);
        assert_eq!(p.offset(), 20);
        assert_eq!(p.limit(), 10);
    }

    #[test]
    fn test_pagination_default() {
        let p = Pagination::default();
        assert_eq!(p.page, 0);
        assert_eq!(p.per_page, 50);
    }
}
