use serde::Serialize;
use sqlx::{postgres::types::Oid, query_as};

use crate::client::WorkspaceClient;

#[derive(Clone, Serialize)]
pub struct PgAttribute {
    /// The table this column belongs to
    pub attrelid: Oid,
    /// The column name
    pub attname: String,
    /// The data type of this column (zero for a dropped column)
    pub atttypid: Oid,
    /// SYNTHESIZED: textual representation of attribute type
    pub regtype: String,
    /// A copy of pg_type.typlen of this column's type
    pub attlen: i16,
    /// The number of the column. Ordinary columns are numbered from 1 up. System columns, such as ctid, have (arbitrary) negative numbers.
    pub attnum: i16,
    /// Number of dimensions, if the column is an array type; otherwise 0.
    /// (Presently, the number of dimensions of an array is not enforced, so any
    /// nonzero value effectively means “it's an array”.)
    pub attndims: i16,
    /// This represents a not-null constraint.
    pub attnotnull: Option<bool>,
    /// This column has a default expression or generation expression, in which case there will be a corresponding entry in the pg_attrdef catalog that actually defines the expression. (Check attgenerated to determine whether this is a default or a generation expression.)
    pub atthasdef: bool,
    /// This column has a value which is used where the column is entirely missing from the row, as happens when a column is added with a non-volatile DEFAULT value after the row is created. The actual value used is stored in the attmissingval column.
    pub atthasmissing: bool,
    /// If a zero byte (''), then not an identity column. Otherwise, a = generated always, d = generated by default.
    pub attidentity: Option<i8>,
    /// If a zero byte (''), then not a generated column. Otherwise, s = stored. (Other values might be added in the future.)
    pub attgenerated: Option<i8>,
    /// This column has been dropped and is no longer valid. A dropped column is still physically present in the table, but is ignored by the parser and so cannot be accessed via SQL.
    pub attisdropped: bool,
    /// This column is defined locally in the relation. Note that a column can be locally defined and inherited simultaneously.
    pub attislocal: bool,
    // /// Column-level access privileges, if any have been granted specifically on this column
    // pub attacl: Option<Vec<String>>,
    /// Attribute-level options, as “keyword=value” strings
    pub attoptions: Option<Vec<String>>,
    /// Attribute-level foreign data wrapper options, as “keyword=value” strings
    pub attfdwoptions: Option<Vec<String>>,
}

impl PgAttribute {
    pub fn all_for_rel(rel_oid: Oid) -> AllForRelQuery {
        AllForRelQuery { rel_oid }
    }

    pub fn pkeys_for_rel(rel_oid: Oid) -> PkeysForRelQuery {
        PkeysForRelQuery { rel_oid }
    }
}

#[derive(Clone, Debug)]
pub struct AllForRelQuery {
    rel_oid: Oid,
}

impl AllForRelQuery {
    pub async fn fetch_all(
        self,
        client: &mut WorkspaceClient,
    ) -> Result<Vec<PgAttribute>, sqlx::Error> {
        query_as!(
            PgAttribute,
            r#"
select
    attrelid,
    attname,
    atttypid,
    atttypid::regtype::text as "regtype!",
    attlen,
    attnum,
    attndims,
    attnotnull as "attnotnull?",
    atthasdef,
    atthasmissing,
    attidentity,
    attgenerated,
    attisdropped,
    attislocal,
    attoptions,
    attfdwoptions
from pg_attribute
where attrelid = $1 and attnum > 0 and not attisdropped
"#,
            &self.rel_oid
        )
        .fetch_all(client.get_conn())
        .await
    }
}

#[derive(Clone, Debug)]
pub struct PkeysForRelQuery {
    rel_oid: Oid,
}

impl PkeysForRelQuery {
    pub async fn fetch_all(
        self,
        client: &mut WorkspaceClient,
    ) -> Result<Vec<PgAttribute>, sqlx::Error> {
        query_as!(
            PgAttribute,
            r#"
select
    a.attrelid as attrelid,
    a.attname as attname,
    a.atttypid as atttypid,
    atttypid::regtype::text as "regtype!",
    a.attlen as attlen,
    a.attnum as attnum,
    a.attndims as attndims,
    a.attnotnull as "attnotnull?",
    a.atthasdef as atthasdef,
    a.atthasmissing as atthasmissing,
    a.attidentity as attidentity,
    a.attgenerated as attgenerated,
    a.attisdropped as attisdropped,
    a.attislocal as attislocal,
    a.attoptions as attoptions,
    a.attfdwoptions as attfdwoptions
from pg_attribute a
    join pg_index i
        on a.attrelid = i.indrelid
            and a.attnum = any(i.indkey)
where i.indrelid = $1 and i.indisprimary;
    "#,
            &self.rel_oid
        )
        .fetch_all(client.get_conn())
        .await
    }
}
