use {
    super::Build,
    crate::{
        ast::{AlterTableOperation, Statement},
        ast_builder::ColumnDefNode,
        result::Result,
    },
};

#[derive(Clone, Debug)]
pub struct AlterTableNode {
    table_name: String,
}

impl AlterTableNode {
    pub fn new(table_name: String) -> Self {
        Self { table_name }
    }

    pub fn add_column<T: Into<ColumnDefNode>>(self, column: T) -> AddColumnNode {
        AddColumnNode {
            table_node: self,
            column_def: column.into(),
        }
    }

    pub fn drop_column(self, column_name: &str) -> DropColumnNode {
        DropColumnNode {
            table_node: self,
            column_name: column_name.to_owned(),
            if_exists: false,
        }
    }

    pub fn drop_column_if_exists(self, column_name: &str) -> DropColumnNode {
        DropColumnNode {
            table_node: self,
            column_name: column_name.to_owned(),
            if_exists: true,
        }
    }

    pub fn rename_column(self, old_name: &str, new_name: &str) -> RenameColumnNode {
        RenameColumnNode {
            table_node: self,
            old_column_name: old_name.to_owned(),
            new_column_name: new_name.to_owned(),
        }
    }

    pub fn rename_table(self, new_table_name: &str) -> RenameTableNode {
        RenameTableNode {
            table_node: self,
            new_table_name: new_table_name.to_owned(),
        }
    }
}

pub struct AddColumnNode {
    table_node: AlterTableNode,
    column_def: ColumnDefNode,
}

impl Build for AddColumnNode {
    fn build(self) -> Result<Statement> {
        let table_name = self.table_node.table_name;
        let operation = AlterTableOperation::AddColumn {
            column_def: self.column_def.try_into()?,
        };
        Ok(Statement::AlterTable {
            name: table_name,
            operation,
        })
    }
}

pub struct DropColumnNode {
    table_node: AlterTableNode,
    column_name: String,
    if_exists: bool,
}

impl Build for DropColumnNode {
    fn build(self) -> Result<Statement> {
        let table_name = self.table_node.table_name;
        let operation = AlterTableOperation::DropColumn {
            column_name: self.column_name,
            if_exists: self.if_exists,
        };
        Ok(Statement::AlterTable {
            name: table_name,
            operation,
        })
    }
}

pub struct RenameColumnNode {
    table_node: AlterTableNode,
    old_column_name: String,
    new_column_name: String,
}

impl Build for RenameColumnNode {
    fn build(self) -> Result<Statement> {
        let table_name = self.table_node.table_name;
        let operation = AlterTableOperation::RenameColumn {
            old_column_name: self.old_column_name,
            new_column_name: self.new_column_name,
        };
        Ok(Statement::AlterTable {
            name: table_name,
            operation,
        })
    }
}

pub struct RenameTableNode {
    table_node: AlterTableNode,
    new_table_name: String,
}

impl Build for RenameTableNode {
    fn build(self) -> Result<Statement> {
        let old_table_name = self.table_node.table_name;
        let operation = AlterTableOperation::RenameTable {
            table_name: self.new_table_name,
        };
        Ok(Statement::AlterTable {
            name: old_table_name,
            operation,
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::ast_builder::{Build, table, test};

    #[test]
    fn alter_table() {
        let actual = table("Foo")
            .alter_table()
            .add_column("opt BOOLEAN NULL")
            .build();
        let expected = "ALTER TABLE Foo ADD COLUMN opt BOOLEAN NULL";
        test(&actual, expected);

        let actual = table("Foo").alter_table().drop_column("col_name").build();
        let expected = "ALTER TABLE Foo DROP COLUMN col_name";
        test(&actual, expected);

        let actual = table("Foo")
            .alter_table()
            .drop_column_if_exists("col_name")
            .build();
        let expected = "ALTER TABLE Foo DROP COLUMN IF EXISTS col_name";
        test(&actual, expected);

        let actual = table("Foo")
            .alter_table()
            .rename_column("old", "new")
            .build();
        let expected = "ALTER TABLE Foo RENAME COLUMN old TO new";
        test(&actual, expected);

        let actual = table("Foo")
            .alter_table()
            .rename_table("new_table_name")
            .build();
        let expected = "ALTER TABLE Foo RENAME TO new_table_name";
        test(&actual, expected);
    }
}
