use derive_more::From;
use std::fmt;
use std::fmt::{Display, Formatter};

use crate::sql::expression::identifier::SimpleIdentifier;
use crate::sql::expression::literal::ColumnReference;
use crate::sql::expression::SQLExpression;
use crate::sql::query::TableReference;
use crate::types::struct_type::Struct;
use crate::write_utils::{maybe_newline, maybe_pad, newline_or_space, Indent};

use super::SQLQuery;

#[derive(Debug, Clone, From, PartialEq, Eq)]
pub enum DML {
    Insert(Insert),
    Merge(Merge),
}

impl DML {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        match self {
            DML::Insert(insert) => insert.fmt_indented(f, indentation),
            DML::Merge(merge) => merge.fmt_indented(f, indentation),
        }
    }
}

impl Display for DML {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Insert {
    pub table: TableReference,
    pub schema: Struct,
    pub query: SQLQuery,
}

impl Insert {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        write!(f, "INSERT INTO ")?;
        write!(f, "{} (", self.table)?;
        for (i, si) in self.schema.keys().enumerate() {
            let sql_si: SimpleIdentifier = si.clone().into();
            sql_si.fmt_indented(f, Indent::default())?;
            if i != self.schema.len() - 1 {
                write!(f, ", ")?;
            }
        }
        write!(f, ")")?;
        newline_or_space(f, indentation)?;
        self.query.fmt_indented(f, indentation)
    }
}

impl Display for Insert {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Merge {
    pub table_alias: SimpleIdentifier,
    pub query_alias: SimpleIdentifier,
    pub table: TableReference,
    pub search_condition: SQLExpression,
    pub when_clauses: Vec<MergeWhenClause>,
    pub query: SQLQuery,
}

impl Merge {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        write!(f, "MERGE INTO ")?;
        write!(f, "{} AS {}", self.table, self.table_alias.to_string())?;
        newline_or_space(f, indentation)?;
        write!(f, "USING (")?;
        maybe_newline(f, indentation)?;
        self.query.fmt_indented(f, indentation)?;
        maybe_newline(f, indentation)?;
        write!(f, ") AS {}", self.query_alias.to_string())?;
        newline_or_space(f, indentation)?;
        write!(f, "ON ")?;
        self.search_condition.fmt_indented(f, indentation)?;
        newline_or_space(f, indentation)?;
        let clause_count = self.when_clauses.len();
        for (i, mc) in self.when_clauses.iter().enumerate() {
            mc.fmt_indented(f, indentation)?;
            if i < clause_count - 1 {
                newline_or_space(f, indentation)?;
            } else {
                maybe_newline(f, indentation)?;
            }
        }
        Ok(())
    }
}

impl Display for Merge {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, From, PartialEq, Eq)]
pub enum MergeWhenClause {
    Matched(MergeWhenMatched),
    NotMatched(MergeWhenNotMatched),
}

impl MergeWhenClause {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        match self {
            MergeWhenClause::Matched(when_matched) => when_matched.fmt_indented(f, indentation),
            MergeWhenClause::NotMatched(when_not_matched) => {
                when_not_matched.fmt_indented(f, indentation)
            }
        }
    }
}

impl Display for MergeWhenClause {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MergeWhenMatched {
    then: MergeAction,
}

impl MergeWhenMatched {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        write!(f, "WHEN MATCHED")?;
        newline_or_space(f, indentation)?;
        maybe_pad(f, indentation.nested())?;
        write!(f, "THEN ")?;
        self.then.fmt_indented(f, indentation)
    }
}

impl Display for MergeWhenMatched {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MergeWhenNotMatched {
    then: MergeAction,
}

impl MergeWhenNotMatched {
    pub fn new(then: MergeAction) -> Self {
        Self { then }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        write!(f, "WHEN NOT MATCHED")?;
        newline_or_space(f, indentation)?;
        maybe_pad(f, indentation.nested())?;
        write!(f, "THEN ")?;
        self.then.fmt_indented(f, indentation)
    }
}

impl Display for MergeWhenNotMatched {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, From, PartialEq, Eq)]
pub enum MergeAction {
    Delete,
    Insert(MergeInsert),
    Update(MergeUpdate),
}

impl MergeAction {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        match self {
            MergeAction::Delete => write!(f, "DELETE"),
            MergeAction::Insert(insert) => insert.fmt_indented(f, indentation),
            MergeAction::Update(update) => update.fmt_indented(f, indentation),
        }
    }
}

impl Display for MergeAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MergeInsert {
    columns: Vec<ColumnReference>,
}

impl MergeInsert {
    pub fn new(columns: Vec<ColumnReference>) -> Self {
        Self { columns }
    }

    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        write!(f, "INSERT (")?;
        let column_length = self.columns.len();
        for (i, c) in self.columns.iter().enumerate() {
            c.fmt_indented(f, indentation)?;
            if i != column_length - 1 {
                write!(f, ", ")?;
            }
        }
        write!(f, ") VALUES (")?;
        for (i, c) in self.columns.iter().enumerate() {
            c.prefixed_with("source").fmt_indented(f, indentation)?;
            if i != column_length - 1 {
                write!(f, ", ")?;
            }
        }
        write!(f, ")")
    }
}

impl Display for MergeInsert {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MergeUpdate {
    columns: Vec<ColumnReference>,
}

impl MergeUpdate {
    pub fn fmt_indented(&self, f: &mut Formatter<'_>, indentation: Indent) -> fmt::Result {
        write!(f, "UPDATE SET (")?;
        newline_or_space(f, indentation)?;
        for (i, c) in self.columns.iter().enumerate() {
            maybe_pad(f, indentation)?;
            if i > 0 {
                write!(f, ", ")?;
            }
            c.fmt_indented(f, indentation.nested())?;
            write!(f, " = ",)?;
            c.prefixed_with("source")
                .fmt_indented(f, indentation.nested())?;
        }
        write!(f, ")")
    }
}

impl Display for MergeUpdate {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.fmt_indented(f, Indent::default())
    }
}
