use serde::Deserialize;

use crate::{
    diff::{ChangeType, Diff, DiffOptions, DiffSql},
    sql::{quote_ident, StatementBuilder},
};

/// A table column definition.
#[derive(Debug, Eq, PartialEq, Deserialize)]
pub struct Column {
    /// Column name.
    pub name: String,
    /// PostgreSQL type name for the column.
    pub r#type: String,
    /// Whether the column is declared `NOT NULL`.
    pub notnull: bool,
    /// Name of the backing `NOT NULL` constraint, when the server exposes one
    /// (PostgreSQL 18+). `None` on older servers, which do not catalogue
    /// `NOT NULL` constraints by name.
    #[serde(default)]
    pub notnull_name: Option<String>,
    /// Identity generation mode.
    pub identity: Identity,
    /// Generated column mode.
    pub generated: Generated,
    /// Default or generated expression payload.
    pub default: Option<String>,
}

impl Column {
    /// Renders the column definition used in `CREATE TABLE`.
    pub fn sql(&self) -> String {
        // https://www.postgresql.org/docs/16/sql-createtable.html
        let mut s = StatementBuilder::new();
        s.ident(&self.name);
        s.part(&self.r#type); // FIXME quoting needed for user types/tables?
        match self.generated {
            Generated::No => {}
            Generated::Stored => {
                s.part("GENERATED ALWAYS AS");
                s.part(format!("({})", self.default.as_ref().unwrap()));
                s.part("STORED");
            }
        }
        match self.identity {
            Identity::No => {}
            Identity::Default => {
                s.part("GENERATED BY DEFAULT AS IDENTITY");
                if let Some(d) = &self.default {
                    s.part(format!("({})", d));
                }
            }
            Identity::Always => {
                s.part("GENERATED ALWAYS AS IDENTITY");
                if let Some(d) = &self.default {
                    s.part(format!("({})", d));
                }
            }
        }
        if self.generated == Generated::No {
            if let Some(default) = &self.default {
                s.part("DEFAULT");
                s.part(default);
            }
        }
        if self.notnull {
            s.part("NOT NULL");
        }
        s.to_string()
    }
    /// Renders the `ADD COLUMN` clause for the column.
    pub fn create_sql(&self) -> String {
        format!("ADD COLUMN {}", self.sql())
    }
    /// Renders the `DROP COLUMN` clause for the column.
    pub fn drop_sql(&self) -> String {
        format!("DROP COLUMN {}", quote_ident(&self.name))
    }

    /// Computes the column alteration clauses needed to reach `new` from `old`.
    pub fn alter_changes(old: &Self, new: &Self) -> Vec<(ChangeType, String)> {
        if old != new && (old.generated != Generated::No || new.generated != Generated::No) {
            return vec![(
                ChangeType::Unsupported,
                new.unsupported_alter_warning_sql(old),
            )];
        }

        let mut sql = Vec::new();
        if old.r#type != new.r#type {
            sql.push((
                ChangeType::AlterColumn,
                format!(
                    "ALTER COLUMN {} TYPE {}",
                    quote_ident(&new.name),
                    new.r#type
                ),
            ));
        }

        match (&old.identity, &new.identity) {
            (Identity::No, Identity::No) => {}
            (Identity::No, Identity::Default) => {
                sql.push((
                    ChangeType::AlterColumn,
                    format!(
                        "ALTER COLUMN {} ADD GENERATED BY DEFAULT AS IDENTITY",
                        quote_ident(&new.name)
                    ),
                ));
            }
            (Identity::No, Identity::Always) => {
                sql.push((
                    ChangeType::AlterColumn,
                    format!(
                        "ALTER COLUMN {} ADD GENERATED ALWAYS AS IDENTITY",
                        quote_ident(&new.name)
                    ),
                ));
            }
            (Identity::Default, Identity::Always) => {
                sql.push((
                    ChangeType::AlterColumn,
                    format!(
                        "ALTER COLUMN {} SET GENERATED ALWAYS",
                        quote_ident(&new.name)
                    ),
                ));
            }
            (Identity::Always, Identity::Default) => {
                sql.push((
                    ChangeType::AlterColumn,
                    format!(
                        "ALTER COLUMN {} SET GENERATED BY DEFAULT",
                        quote_ident(&new.name)
                    ),
                ));
            }
            (Identity::Default, Identity::No) | (Identity::Always, Identity::No) => {
                sql.push((
                    ChangeType::AlterColumn,
                    format!(
                        "ALTER COLUMN {} DROP IDENTITY IF EXISTS",
                        quote_ident(&new.name)
                    ),
                ));
            }
            _ => {}
        }

        if old.generated == Generated::No
            && new.generated == Generated::No
            && old.default != new.default
        {
            match &new.default {
                Some(default) => sql.push((
                    ChangeType::AlterColumn,
                    format!(
                        "ALTER COLUMN {} SET DEFAULT {}",
                        quote_ident(&new.name),
                        default
                    ),
                )),
                None => sql.push((
                    ChangeType::AlterColumn,
                    format!("ALTER COLUMN {} DROP DEFAULT", quote_ident(&new.name)),
                )),
            }
        }

        if old.notnull != new.notnull {
            sql.push((
                ChangeType::AlterColumn,
                format!(
                    "ALTER COLUMN {} {}",
                    quote_ident(&new.name),
                    if new.notnull {
                        "SET NOT NULL"
                    } else {
                        "DROP NOT NULL"
                    }
                ),
            ));
        }

        sql
    }

    /// Renders a warning block for generated-column changes that need manual work.
    pub fn unsupported_alter_warning_sql(&self, previous: &Self) -> String {
        format!(
            "-- WARNING: column {} has an unsupported schema change and no safe automatic migration was generated.\n\
-- Previous generated state: {:?}\n\
-- Target generated state: {:?}\n\
-- Previous expression/default payload: {}\n\
-- Target expression/default payload: {}\n\
-- Suggested manual approach:\n\
-- 1. Add a replacement column with the desired generated definition.\n\
-- 2. Rewrite dependent indexes, constraints, or queries.\n\
-- 3. Drop the old column and rename the replacement column.\n\
",
            quote_ident(&self.name),
            previous.generated,
            self.generated,
            previous.default.as_deref().unwrap_or("NULL"),
            self.default.as_deref().unwrap_or("NULL"),
        )
    }
}

/// Generated-column state reported by PostgreSQL.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize)]
pub enum Generated {
    /// Not a generated column.
    #[serde(rename = "")]
    No,
    /// Stored generated column.
    #[serde(rename = "s")]
    Stored,
}

/// Identity-column state reported by PostgreSQL.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize)]
pub enum Identity {
    /// Not an identity column.
    #[serde(rename = "")]
    No,
    /// `GENERATED ALWAYS AS IDENTITY`
    #[serde(rename = "a")]
    Always,
    /// `GENERATED BY DEFAULT AS IDENTITY`
    #[serde(rename = "d")]
    Default,
}

impl DiffSql for Diff<'_, Column> {
    fn sql(&self, _opts: &DiffOptions) -> Vec<(ChangeType, String)> {
        let mut v = Vec::new();
        for a in &self.a_only {
            v.push((ChangeType::DropColumn, a.drop_sql()));
        }
        for (a, b) in &self.a_and_b {
            if a != b {
                v.extend(Column::alter_changes(a, b));
            }
        }
        for b in &self.b_only {
            v.push((ChangeType::CreateColumn, b.create_sql()));
        }
        v
    }
}
