// SPDX-FileCopyrightText: 2025-2026 RAprogramm <andrey.rozanov.vl@gmail.com>
// SPDX-License-Identifier: MIT

//! Entity-level attribute parsing with darling.
//!
//! This module defines the internal [`EntityAttrs`] structure used for
//! parsing `#[entity(...)]` attributes. This is an implementation detail;
//! the public API uses [`EntityDef`](super::EntityDef).
//!
//! # Supported Attributes
//!
//! | Attribute | Required | Default | Description |
//! |-----------|----------|---------|-------------|
//! | `table` | Yes | — | Database table name |
//! | `schema` | No | — | Database schema (omitted = no prefix in SQL) |
//! | `sql` | No | `Full` | SQL generation level |
//! | `dialect` | No | `Postgres` | Database dialect |
//! | `uuid` | No | `V7` | UUID version for IDs |
//! | `error` | No | `sqlx::Error` | Custom error type |
//! | `soft_delete` | No | `false` | Enable soft delete |
//! | `returning` | No | `Full` | RETURNING clause mode |
//! | `events` | No | `false` | Generate lifecycle events |
//! | `hooks` | No | `false` | Generate lifecycle hooks trait |
//! | `commands` | No | `false` | Generate CQRS command pattern |
//! | `policy` | No | `false` | Generate authorization policy trait |
//! | `streams` | No | `false` | Enable real-time streaming via LISTEN/NOTIFY |

use darling::FromDeriveInput;
use syn::{Ident, Visibility};

use super::upsert::UpsertDef;
use crate::entity::parse::{DatabaseDialect, ReturningMode, SqlLevel, UuidVersion};

/// Events configuration parsed from `#[entity(events)]` or
/// `#[entity(events(outbox))]`.
///
/// | Form | Meaning |
/// |------|---------|
/// | `events` | Generate the `{Entity}Event` enum |
/// | `events(outbox)` | Additionally enqueue events into the transactional outbox |
#[derive(Debug, Clone, Copy, Default)]
pub struct EventsAttr {
    /// Whether event generation is enabled at all.
    pub enabled: bool,

    /// Whether transactional-outbox delivery is enabled.
    pub outbox: bool
}

impl darling::FromMeta for EventsAttr {
    fn from_word() -> darling::Result<Self> {
        Ok(Self {
            enabled: true,
            outbox:  false
        })
    }

    fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
        let mut outbox = false;
        for item in items {
            match item {
                darling::ast::NestedMeta::Meta(syn::Meta::Path(path))
                    if path.is_ident("outbox") =>
                {
                    outbox = true;
                }
                other => {
                    return Err(darling::Error::custom(
                        "unknown events option; expected `events` or `events(outbox)`"
                    )
                    .with_span(other));
                }
            }
        }
        Ok(Self {
            enabled: true,
            outbox
        })
    }
}

/// Migrations configuration parsed from `#[entity(migrations)]` or
/// `#[entity(migrations(...))]`.
///
/// | Form | Meaning |
/// |------|---------|
/// | `migrations` | `MIGRATION_UP` / `MIGRATION_DOWN` constants |
/// | `migrations(touch_updated_at)` | + shared trigger keeping `updated_at` fresh |
/// | `migrations(audit)` | + JSONB audit-log table and trigger |
/// | `migrations(extensions = "pg_trgm, pgcrypto")` | + `CREATE EXTENSION` statements |
#[derive(Debug, Clone, Default)]
pub struct MigrationsAttr {
    /// Whether migration generation is enabled at all.
    pub enabled: bool,

    /// Emit the shared `entity_touch_updated_at()` function and a
    /// per-table trigger bumping `updated_at` on every UPDATE.
    pub touch_updated_at: bool,

    /// Emit the `entity_audit_log` table and a per-table trigger
    /// recording `to_jsonb(OLD/NEW)` diffs.
    pub audit: bool,

    /// Extensions to create before the table DDL.
    pub extensions: Vec<String>
}

impl darling::FromMeta for MigrationsAttr {
    fn from_word() -> darling::Result<Self> {
        Ok(Self {
            enabled: true,
            ..Self::default()
        })
    }

    fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
        let mut attr = Self {
            enabled: true,
            ..Self::default()
        };
        for item in items {
            match item {
                darling::ast::NestedMeta::Meta(syn::Meta::Path(path))
                    if path.is_ident("touch_updated_at") =>
                {
                    attr.touch_updated_at = true;
                }
                darling::ast::NestedMeta::Meta(syn::Meta::Path(path))
                    if path.is_ident("audit") =>
                {
                    attr.audit = true;
                }
                darling::ast::NestedMeta::Meta(syn::Meta::NameValue(nv))
                    if nv.path.is_ident("extensions") =>
                {
                    let syn::Expr::Lit(syn::ExprLit {
                        lit: syn::Lit::Str(lit),
                        ..
                    }) = &nv.value
                    else {
                        return Err(darling::Error::custom(
                            "extensions expects a comma-separated string"
                        )
                        .with_span(&nv.value));
                    };
                    attr.extensions = lit
                        .value()
                        .split(',')
                        .map(str::trim)
                        .filter(|s| !s.is_empty())
                        .map(str::to_string)
                        .collect();
                }
                other => {
                    return Err(darling::Error::custom(
                        "unknown migrations option; expected touch_updated_at, audit or extensions = \"...\""
                    )
                    .with_span(other));
                }
            }
        }
        Ok(attr)
    }
}

/// Default error type path for SQL implementations.
///
/// Used when no custom error type is specified.
/// Uses `parse_quote!` for compile-time validation.
pub fn default_error_type() -> syn::Path {
    syn::parse_quote!(sqlx::Error)
}

/// Entity-level attributes parsed from `#[entity(...)]`.
///
/// This is an internal struct used by darling for parsing.
/// The public API uses [`EntityDef`](super::EntityDef) which combines these
/// attributes with parsed field definitions.
///
/// # Example
///
/// ```rust,ignore
/// #[entity(
///     table = "users",
///     schema = "core",
///     sql = "full",
///     dialect = "postgres",
///     uuid = "v7",
///     soft_delete,
///     returning = "full"
/// )]
/// ```
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(entity), supports(struct_named), allow_unknown_fields)]
pub struct EntityAttrs {
    /// Struct identifier (e.g., `User`).
    pub ident: Ident,

    /// Struct visibility (e.g., `pub`, `pub(crate)`).
    pub vis: Visibility,

    /// Database table name.
    ///
    /// This is a required attribute with no default value.
    /// The macro will fail with a clear error if not provided.
    pub table: String,

    /// Database schema name.
    ///
    /// When omitted, SQL queries use just the table name without schema prefix.
    /// When specified (any non-empty value), SQL queries use `"schema.table"`
    /// format.
    #[darling(default)]
    pub schema: String,

    /// SQL generation level.
    ///
    /// Defaults to [`SqlLevel::Full`] if not specified.
    #[darling(default)]
    pub sql: SqlLevel,

    /// Database dialect.
    ///
    /// Defaults to [`DatabaseDialect::Postgres`] if not specified.
    #[darling(default)]
    pub dialect: DatabaseDialect,

    /// UUID version for ID generation.
    ///
    /// Defaults to [`UuidVersion::V7`] if not specified.
    #[darling(default)]
    pub uuid: UuidVersion,

    /// Custom error type for repository implementation.
    ///
    /// Defaults to `sqlx::Error` if not specified.
    /// The custom type must implement `From<sqlx::Error>`.
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// #[entity(table = "users", error = "AppError")]
    /// #[entity(table = "users", error = "crate::errors::DbError")]
    /// ```
    #[darling(default = "default_error_type")]
    pub error: syn::Path,

    /// Enable soft delete for this entity.
    ///
    /// When enabled, the entity must have a `deleted_at: Option<DateTime<Utc>>`
    /// field. The `delete` method will set this timestamp instead of removing
    /// the row, and all queries will automatically filter out deleted records.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// #[entity(table = "users", soft_delete)]
    /// pub struct User {
    ///     #[id]
    ///     pub id: Uuid,
    ///     pub deleted_at: Option<DateTime<Utc>>,
    /// }
    /// ```
    #[darling(default)]
    pub soft_delete: bool,

    /// RETURNING clause mode for INSERT/UPDATE operations.
    ///
    /// Controls what data is fetched back from the database:
    /// - `full` (default): Use `RETURNING *` to get all fields
    /// - `id`: Use `RETURNING id` to get only the primary key
    /// - `none`: No RETURNING clause, return pre-built entity
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// #[entity(table = "users", returning = "full")]
    /// #[entity(table = "logs", returning = "none")]
    /// ```
    #[darling(default)]
    pub returning: ReturningMode,

    /// Generate lifecycle event enum.
    ///
    /// When enabled, generates a `{Entity}Event` enum with variants for
    /// each lifecycle operation (Created, Updated, Deleted, etc.).
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// #[entity(table = "users", events)]
    /// pub struct User { ... }
    ///
    /// // Generates:
    /// pub enum UserEvent {
    ///     Created(User),
    ///     Updated { old: User, new: User },
    ///     Deleted { id: Uuid },
    /// }
    /// ```
    #[darling(default)]
    pub events: EventsAttr,

    /// Generate lifecycle hooks trait.
    ///
    /// When enabled, generates a `{Entity}Hooks` trait with before/after
    /// methods for each CRUD operation. Default implementations are no-ops.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// #[entity(table = "users", hooks)]
    /// pub struct User { ... }
    ///
    /// // Implement hooks:
    /// impl UserHooks for MyRepo {
    ///     async fn before_create(&self, dto: &mut CreateUserRequest) -> Result<(), Error> {
    ///         validate(&dto)?;
    ///         Ok(())
    ///     }
    /// }
    /// ```
    #[darling(default)]
    pub hooks: bool,

    /// Generate CQRS-style command pattern.
    ///
    /// When enabled, processes `#[command(...)]` attributes to generate:
    /// - Command structs (e.g., `RegisterUser`, `UpdateEmailUser`)
    /// - Command enum (`UserCommand`)
    /// - Result enum (`UserCommandResult`)
    /// - Handler trait (`UserCommandHandler`)
    #[darling(default)]
    pub commands: bool,

    /// Generate authorization policy trait.
    ///
    /// When enabled, generates:
    /// - `{Entity}Policy` trait with `can_create`, `can_read`, etc.
    /// - `{Entity}AllowAllPolicy` default implementation
    /// - `{Entity}PolicyRepository` wrapper with authorization checks
    #[darling(default)]
    pub policy: bool,

    /// Enable real-time streaming via Postgres LISTEN/NOTIFY.
    ///
    /// When enabled (requires `events`), generates:
    /// - `{Entity}Subscriber` for async event streaming
    /// - NOTIFY calls in CRUD operations
    /// - `CHANNEL` constant with notification channel name
    #[darling(default)]
    pub streams: bool,

    /// Enable transaction support.
    ///
    /// When enabled, generates:
    /// - `{Entity}TransactionRepo` adapter for use in transactions
    /// - `with_{entity}()` method on `Transaction` builder
    /// - Accessor methods on `TransactionContext`
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// #[entity(table = "accounts", transactions)]
    /// pub struct Account { ... }
    ///
    /// // Usage:
    /// Transaction::new(&pool)
    ///     .run(async |ctx| {
    ///         ctx.accounts().create(dto).await
    ///     })
    ///     .await?;
    /// ```
    #[darling(default)]
    pub transactions: bool,

    /// Enable migration generation.
    ///
    /// When enabled, generates:
    /// - `{Entity}::MIGRATION_UP` — SQL to create the table
    /// - `{Entity}::MIGRATION_DOWN` — SQL to drop the table
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// #[entity(table = "users", migrations)]
    /// pub struct User {
    ///     #[id]
    ///     pub id: Uuid,
    ///     #[column(unique, index)]
    ///     pub email: String,
    /// }
    ///
    /// // Generated:
    /// // User::MIGRATION_UP → CREATE TABLE users (...)
    /// // User::MIGRATION_DOWN → DROP TABLE users CASCADE
    /// ```
    #[darling(default)]
    pub migrations: MigrationsAttr,

    /// Enable aggregate root pattern.
    ///
    /// When enabled, the entity is treated as an aggregate root in DDD.
    /// Generates `New{Name}` structs and a transactional `save` method.
    #[darling(default)]
    pub aggregate_root: bool,

    /// Upsert configuration.
    ///
    /// When present, generates an `upsert` repository method using
    /// `INSERT ... ON CONFLICT`. Conflict columns must carry a uniqueness
    /// guarantee (`#[id]`, `#[column(unique)]` or a `unique_index(...)`).
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// #[entity(table = "users", upsert(conflict = "email"))]
    /// #[entity(table = "members", unique_index(tenant_id, email), upsert(conflict = "tenant_id, email", action = "nothing"))]
    /// ```
    #[darling(default)]
    pub upsert: Option<UpsertDef>,

    /// Map database constraint violations to typed errors.
    ///
    /// When enabled, generated write methods resolve violated
    /// constraint names against the entity's known constraints and
    /// surface `entity_core::ConstraintError` through the repository
    /// `Error` type, which must additionally implement
    /// `From<entity_core::ConstraintError>`.
    #[darling(default)]
    pub typed_constraints: bool
}
