use super::*;

#[allow(missing_docs)]
impl<M: Model> QueryBuilder<M> {
    pub(crate) fn format_column_for_db(&self, db_type: DatabaseType, column: &str) -> String {
        let trimmed = column.trim();
        let parts: Vec<&str> = trimmed.split_whitespace().collect();

        match parts.as_slice() {
            [identifier] => db_sql::format_column_or_trusted_expression(db_type, identifier),
            [identifier, direction]
                if direction.eq_ignore_ascii_case("asc")
                    || direction.eq_ignore_ascii_case("desc") =>
            {
                db_sql::format_identifier_reference(db_type, identifier)
                    .map(|identifier| format!("{} {}", identifier, direction.to_ascii_uppercase()))
                    .unwrap_or_else(|| trimmed.to_string())
            }
            [identifier, as_keyword, alias] if as_keyword.eq_ignore_ascii_case("as") => {
                match (
                    db_sql::format_identifier_reference(db_type, identifier),
                    db_sql::format_identifier_reference(db_type, alias),
                ) {
                    (Some(identifier), Some(alias)) => format!("{} AS {}", identifier, alias),
                    _ => trimmed.to_string(),
                }
            }
            _ => trimmed.to_string(),
        }
    }

    fn format_select_column_for_db(
        &self,
        db_type: DatabaseType,
        table: &str,
        column: &str,
    ) -> String {
        let trimmed = column.trim();
        let parts: Vec<&str> = trimmed.split_whitespace().collect();

        match parts.as_slice() {
            [identifier]
                if !identifier.contains('(')
                    && !identifier.contains('*')
                    && db_sql::format_identifier_reference(db_type, identifier).is_some() =>
            {
                if identifier.contains('.') {
                    self.format_column_for_db(db_type, identifier)
                } else {
                    format!(
                        "{}.{}",
                        db_sql::quote_ident(db_type, table),
                        db_sql::quote_ident(db_type, identifier)
                    )
                }
            }
            [identifier, as_keyword, alias]
                if as_keyword.eq_ignore_ascii_case("as")
                    && !identifier.contains('(')
                    && !identifier.contains('*')
                    && db_sql::format_identifier_reference(db_type, identifier).is_some()
                    && db_sql::format_identifier_reference(db_type, alias).is_some() =>
            {
                let identifier = if identifier.contains('.') {
                    self.format_column_for_db(db_type, identifier)
                } else {
                    format!(
                        "{}.{}",
                        db_sql::quote_ident(db_type, table),
                        db_sql::quote_ident(db_type, identifier)
                    )
                };

                format!("{} AS {}", identifier, db_sql::quote_ident(db_type, alias))
            }
            _ => trimmed.to_string(),
        }
    }

    fn build_select_clause_sql(&self, db_type: DatabaseType) -> String {
        let table = M::table_name();

        if !self.raw_select_expressions.is_empty() || !self.subquery_select_expressions.is_empty() {
            let mut expressions = self.raw_select_expressions.clone();
            expressions.extend(self.subquery_select_expressions.iter().map(
                |(query_sql, alias)| {
                    format!("({}) AS {}", query_sql, db_sql::quote_ident(db_type, alias))
                },
            ));
            for window_function in &self.window_functions {
                expressions.push(window_function.to_sql_for_db(db_type));
            }
            return format!("SELECT {} ", expressions.join(", "));
        }

        if let Some(columns) = &self.select_columns {
            let mut rendered_columns: Vec<String> = columns
                .iter()
                .map(|column| self.format_select_column_for_db(db_type, table, column))
                .collect();

            for window_function in &self.window_functions {
                rendered_columns.push(window_function.to_sql_for_db(db_type));
            }

            return format!("SELECT {} ", rendered_columns.join(", "));
        }

        let mut select_parts = vec![format!("{}.*", db_sql::quote_ident(db_type, table))];
        for window_function in &self.window_functions {
            select_parts.push(window_function.to_sql_for_db(db_type));
        }
        format!("SELECT {} ", select_parts.join(", "))
    }

    pub(crate) fn append_from_and_join_sql(&self, sql: &mut String, db_type: DatabaseType) {
        sql.push_str(&format!(
            "FROM {} ",
            db_sql::quote_ident(db_type, M::table_name())
        ));

        for join in &self.joins {
            let join_table = if let Some(alias) = &join.alias {
                format!(
                    "{} AS {}",
                    db_sql::quote_ident(db_type, &join.table),
                    db_sql::quote_ident(db_type, alias)
                )
            } else {
                db_sql::quote_ident(db_type, &join.table)
            };

            sql.push_str(&format!(
                "{} {} ON {} = {} ",
                join.join_type.as_sql(),
                join_table,
                self.format_column_for_db(db_type, &join.left_column),
                self.format_column_for_db(db_type, &join.right_column)
            ));
        }
    }

    fn render_having_preview_sql(
        &self,
        db_type: DatabaseType,
        sql_template: &str,
        params: &[serde_json::Value],
    ) -> String {
        if params.is_empty() {
            return sql_template.to_string();
        }

        let mut rendered = String::new();
        let mut params_iter = params.iter();

        for ch in sql_template.chars() {
            if ch == '?' {
                if let Some(value) = params_iter.next() {
                    rendered.push_str(&self.format_preview_value(db_type, value));
                } else {
                    rendered.push('?');
                }
            } else {
                rendered.push(ch);
            }
        }

        rendered
    }

    fn render_having_parameterized_sql(
        &self,
        sql_template: &str,
        params: &[serde_json::Value],
        db_type: DatabaseType,
        next_param_index: &mut usize,
    ) -> String {
        if params.is_empty() {
            return sql_template.to_string();
        }

        if !matches!(db_type, DatabaseType::Postgres) {
            return sql_template.to_string();
        }

        let mut rendered = String::new();
        let mut replaced = 0usize;

        for ch in sql_template.chars() {
            if ch == '?' {
                rendered.push_str(&format!("${}", *next_param_index));
                *next_param_index += 1;
                replaced += 1;
            } else {
                rendered.push(ch);
            }
        }

        debug_assert_eq!(replaced, params.len());
        rendered
    }

    pub(crate) fn materialized_having_conditions(&self, db_type: DatabaseType) -> Vec<String> {
        self.having_conditions
            .iter()
            .enumerate()
            .map(|(index, sql_template)| {
                let params = self
                    .having_bindings
                    .get(index)
                    .map(Vec::as_slice)
                    .unwrap_or(&[]);
                self.render_having_preview_sql(db_type, sql_template, params)
            })
            .collect()
    }

    fn append_group_by_and_having_sql(&self, sql: &mut String, db_type: DatabaseType) {
        if !self.group_by.is_empty() {
            let columns: Vec<String> = self
                .group_by
                .iter()
                .map(|column| self.format_column_for_db(db_type, column))
                .collect();
            sql.push_str(&format!("GROUP BY {} ", columns.join(", ")));
        }

        if !self.having_conditions.is_empty() {
            sql.push_str(&format!(
                "HAVING {} ",
                self.materialized_having_conditions(db_type).join(" AND ")
            ));
        }
    }

    fn append_group_by_and_having_sql_with_params(
        &self,
        sql: &mut String,
        db_type: DatabaseType,
        params: &mut Vec<Value>,
    ) {
        if !self.group_by.is_empty() {
            let columns: Vec<String> = self
                .group_by
                .iter()
                .map(|column| self.format_column_for_db(db_type, column))
                .collect();
            sql.push_str(&format!("GROUP BY {} ", columns.join(", ")));
        }

        if !self.having_conditions.is_empty() {
            let mut next_param_index = params.len() + 1;
            let rendered_having: Vec<String> = self
                .having_conditions
                .iter()
                .enumerate()
                .map(|(index, sql_template)| {
                    let bindings = self
                        .having_bindings
                        .get(index)
                        .map(Vec::as_slice)
                        .unwrap_or(&[]);
                    let clause_sql = self.render_having_parameterized_sql(
                        sql_template,
                        bindings,
                        db_type,
                        &mut next_param_index,
                    );
                    params.extend(bindings.iter().map(crate::internal::json_to_db_value));
                    clause_sql
                })
                .collect();

            sql.push_str(&format!("HAVING {} ", rendered_having.join(" AND ")));
        }
    }

    fn append_cte_sql(&self, sql: &mut String) {
        if self.ctes.is_empty() {
            return;
        }

        let recursive = self.ctes.iter().any(|cte| cte.recursive);
        sql.push_str(if recursive {
            "WITH RECURSIVE "
        } else {
            "WITH "
        });
        let cte_parts: Vec<String> = self.ctes.iter().map(CTE::to_sql).collect();
        sql.push_str(&cte_parts.join(", "));
        sql.push(' ');
    }

    fn append_union_sql(&self, sql: &mut String, wrap_subqueries: bool) {
        for union in &self.unions {
            if wrap_subqueries {
                sql.push_str(&format!(
                    " {} ({})",
                    union.union_type.as_sql(),
                    union.query_sql
                ));
            } else {
                sql.push_str(&format!(
                    " {} {}",
                    union.union_type.as_sql(),
                    union.query_sql
                ));
            }
        }
    }

    fn append_order_limit_offset_sql(&self, sql: &mut String, db_type: DatabaseType) {
        if !self.order_by.is_empty() {
            let order_parts: Vec<String> = self
                .order_by
                .iter()
                .map(|(column, direction)| {
                    format!(
                        "{} {}",
                        self.format_column_for_db(db_type, column),
                        direction.as_str()
                    )
                })
                .collect();
            sql.push_str(&format!(" ORDER BY {}", order_parts.join(", ")));
        }

        if let Some(limit) = self.limit_value {
            sql.push_str(&format!(" LIMIT {}", limit));
        }

        if let Some(offset) = self.offset_value {
            sql.push_str(&format!(" OFFSET {}", offset));
        }
    }

    fn assemble_base_select_sql(&self, db_type: DatabaseType, where_sql: &str) -> String {
        let mut sql = String::new();

        sql.push_str(&self.build_select_clause_sql(db_type));
        self.append_from_and_join_sql(&mut sql, db_type);

        if !where_sql.is_empty() {
            sql.push_str(&format!("WHERE {} ", where_sql));
        }

        self.append_group_by_and_having_sql(&mut sql, db_type);
        sql.trim().to_string()
    }

    fn assemble_select_sql(
        &self,
        db_type: DatabaseType,
        base_sql: &str,
        wrap_union_subqueries: bool,
    ) -> String {
        let mut sql = String::new();

        self.append_cte_sql(&mut sql);
        sql.push_str(base_sql);
        self.append_union_sql(&mut sql, wrap_union_subqueries);
        self.append_order_limit_offset_sql(&mut sql, db_type);

        sql.trim().to_string()
    }

    pub(crate) fn build_base_select_sql(&self) -> String {
        self.build_base_select_sql_for_db(self.db_type_for_sql())
    }

    pub(crate) fn build_base_select_sql_for_db(&self, db_type: DatabaseType) -> String {
        let where_sql = self.build_where_sql_for_db(db_type);
        self.assemble_base_select_sql(db_type, &where_sql)
    }

    pub(crate) fn build_base_select_sql_with_params_for_db(
        &self,
        db_type: DatabaseType,
    ) -> (String, Vec<Value>) {
        let (where_sql, mut params) = self.build_where_clause_with_condition_for_db(db_type);
        let mut sql = String::new();

        sql.push_str(&self.build_select_clause_sql(db_type));
        self.append_from_and_join_sql(&mut sql, db_type);

        if !where_sql.is_empty() {
            sql.push_str(&format!("WHERE {} ", where_sql));
        }

        self.append_group_by_and_having_sql_with_params(&mut sql, db_type, &mut params);
        (sql.trim().to_string(), params)
    }

    pub(crate) fn build_select_sql(&self) -> String {
        self.build_select_sql_for_db(self.db_type_for_sql())
    }

    pub(crate) fn build_select_sql_for_db(&self, db_type: DatabaseType) -> String {
        let base_sql = self.build_base_select_sql_for_db(db_type);
        self.assemble_select_sql(db_type, &base_sql, false)
    }

    pub(crate) fn build_select_sql_with_params_for_db(
        &self,
        db_type: DatabaseType,
    ) -> (String, Vec<Value>) {
        let (base_sql, params) = self.build_base_select_sql_with_params_for_db(db_type);
        (self.assemble_select_sql(db_type, &base_sql, true), params)
    }

    pub(crate) fn build_select_sql_with_params(&self) -> (String, Vec<Value>) {
        self.build_select_sql_with_params_for_db(self.db_type_for_sql())
    }
}
