use super::*;

#[derive(Clone, Debug, Default)]
pub struct SelectCollectionRecordBuilder {
    sql: String,
    table_name: String,
    predicate: Option<CollectionRecordPredicateBuilder>,
    having: Option<CollectionRecordPredicateBuilder>,
    union: Option<Vec<String>>,
    order_by: Option<String>,
    limit: Option<String>,
    offset: Option<String>,
}

impl SelectCollectionRecordBuilder {
    pub(crate) fn new(table_name: String) -> Self {
        Self {
            sql: "SELECT `id`, `deleted_at`".to_string(),
            table_name,
            predicate: None,
            having: None,
            union: None,
            order_by: None,
            limit: None,
            offset: None,
        }
    }

    pub fn column(&mut self, name: &str) -> &mut Self {
        self.sql.push_str(&format!(", `{}`", name));
        self
    }

    pub fn expression(&mut self, expr: &str, name: &str) -> &mut Self {
        self.sql.push_str(&format!(", {} AS `{}`", expr, name));
        self
    }

    pub fn with_version(&mut self, with_version: bool) -> &mut Self {
        if with_version {
            self.version()
        } else {
            self
        }
    }

    pub fn version(&mut self) -> &mut Self {
        self.sql.push_str(", `version`");
        self
    }

    pub fn with_payload(&mut self, with_payload: bool) -> &mut Self {
        if with_payload {
            self.payload()
        } else {
            self
        }
    }

    pub fn payload(&mut self) -> &mut Self {
        self.sql.push_str(", `payload`");
        self
    }

    pub fn with_vector(&mut self, with_vector: bool) -> &mut Self {
        if with_vector {
            self.vector()
        } else {
            self
        }
    }

    pub fn vector(&mut self) -> &mut Self {
        self.sql.push_str(", CAST(vector AS CHAR) as `vector`");
        self
    }

    pub fn limit(&mut self, limit: impl Into<Option<u32>>) -> &mut Self {
        if let Some(limit) = limit.into() {
            self.limit = Some(format!(" LIMIT {}", limit));
        }
        self
    }

    pub fn offset(&mut self, offset: impl Into<Option<u64>>) -> &mut Self {
        if let Some(offset) = offset.into() {
            self.offset = Some(format!(" OFFSET {}", offset));
        }
        self
    }

    pub fn predicate(&mut self) -> &mut CollectionRecordPredicateBuilder {
        self.predicate.get_or_insert_with(Default::default)
    }

    pub fn build(self) -> String {
        let prepend = if self.union.is_some() { "((" } else { "" };
        let mut sql = format!("{}{} FROM `{}`", prepend, self.sql, self.table_name);
        let predicate = self.predicate.unwrap_or_else(|| {
            let mut predicate = CollectionRecordPredicateBuilder::default();
            predicate.not_deleted();
            predicate
        });
        if !predicate.sql.is_empty() {
            sql.push_str(" WHERE ");
            sql.push_str(&predicate.sql);
        }

        if let Some(having) = self.having {
            sql.push_str(" HAVING ");
            sql.push_str(&having.sql);
        }

        if let Some(union) = self.union {
            sql.push(')');
            for query in union {
                sql.push_str(&query);
            }
            sql.push(')')
        }

        if let Some(order_by) = self.order_by {
            sql.push_str(" ORDER BY ");
            sql.push_str(&order_by);
        }

        if let Some(limit) = self.limit {
            sql.push_str(&limit);
        }

        if let Some(offset) = self.offset {
            sql.push_str(&offset);
        }

        sql
    }

    pub fn having(&mut self) -> &mut CollectionRecordPredicateBuilder {
        self.having.get_or_insert_with(Default::default)
    }

    pub fn order_by(&mut self, column: impl AsRef<str>, asc: bool) -> &mut Self {
        let order_by = format!("`{}` {}", column.as_ref(), if asc { "ASC" } else { "DESC" });
        if self.order_by.is_some() {
            let sql = self.order_by.as_mut().unwrap();
            sql.push_str(", ");
            sql.push_str(&order_by);
        } else {
            self.order_by = Some(order_by);
        }
        self
    }

    pub fn union(&mut self, sql: impl AsRef<str>) -> &mut Self {
        self.union
            .get_or_insert_with(Default::default)
            .push(format!(" UNION ({})", sql.as_ref()));
        self
    }
}
