use sea_orm::entity::prelude::*;
use sea_orm::{ConnectionTrait, Statement};
pub use crate::models::_entities::products::{ActiveModel, Column, Entity, Model};
pub type Products = Entity;

#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        if !insert && self.updated_at.is_unchanged() {
            let mut this = self;
            this.updated_at = sea_orm::ActiveValue::Set(chrono::Utc::now().into());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

// implement your read-oriented logic here
impl Model {}

// implement your write-oriented logic here
impl ActiveModel {}

// implement your custom finders, selectors oriented logic here
impl Entity {
    /// Published products matching a free-text query, best matches first.
    ///
    /// Hybrid Postgres search. The `tsvector` full-text match covers the whole
    /// purchasable surface — name, description, tags, variant labels and SKUs
    /// (kept in sync by triggers; see the `product_search_aggregate` migration) —
    /// OR a `pg_trgm` `word_similarity` match on name/description for typo
    /// tolerance ("komprsor" still finds "kompresor"). Both sides run through
    /// `f_unaccent`, so diacritics never matter. Results are ranked by full-text
    /// rank, then trigram closeness of the name, then recency. An empty/blank
    /// query returns nothing — callers fall back to the plain listing.
    /// `published_only` filters to the storefront-visible set; pass `false` for
    /// admin tools that also need to find drafts.
    pub async fn search<C: ConnectionTrait>(
        db: &C,
        query: &str,
        limit: u64,
        published_only: bool,
    ) -> Result<Vec<Model>, DbErr> {
        let q = query.trim();
        if q.is_empty() {
            return Ok(Vec::new());
        }

        // Only the model's own columns are selected; the generated `search_vector`
        // is left out so the row maps cleanly back onto `Model`. `$1` is reused
        // for every occurrence of the query term; `$2` caps the result set.
        let published_clause = if published_only { "p.published = TRUE AND" } else { "" };
        let sql = format!(
            r#"
            SELECT p.created_at, p.updated_at, p.id, p.name, p.slug, p.description,
                   p.short_description, p.view_count, p.published, p.published_at, p.category_id
            FROM products p
            WHERE {published_clause} (
                  p.search_vector @@ websearch_to_tsquery('sk_unaccent', $1)
                  OR word_similarity(f_unaccent($1), f_unaccent(p.name)) > 0.3
                  OR word_similarity(f_unaccent($1), f_unaccent(COALESCE(p.description, ''))) > 0.3
              )
            ORDER BY
                ts_rank(p.search_vector, websearch_to_tsquery('sk_unaccent', $1)) DESC,
                word_similarity(f_unaccent($1), f_unaccent(p.name)) DESC,
                p.published_at DESC NULLS LAST
            LIMIT $2
        "#
        );

        Entity::find()
            .from_raw_sql(Statement::from_sql_and_values(
                db.get_database_backend(),
                &sql,
                [q.into(), (limit as i64).into()],
            ))
            .all(db)
            .await
    }
}
