use nom::{
    AsChar as _, IResult, Parser,
    branch::alt,
    bytes::complete::{is_not, tag, take_till},
    character::char,
    combinator::{opt, value},
    error::ParseError,
    multi::{many0, many1},
    sequence::delimited,
};
use serde::{Deserialize, Serialize};
use sqlx::{
    Decode, Postgres,
    error::BoxDynError,
    postgres::{PgHasArrayType, PgTypeInfo, PgValueRef},
};
use strum::IntoEnumIterator as _;

/// This type will automatically decode Postgres "aclitem" values, provided that
/// the query is cast to a TEXT type and selected with type annotations. For
/// example:
/// ```sql
/// select datacl::text[] as "datacl: Vec<PgAclItem>" from pg_database;
/// ```
/// The TEXT cast is necessary because the aclitem type itself is incompatible
/// with binary value format, which makes it incompatible with SQLx.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct PgAclItem {
    pub grantee: String,
    pub privileges: Vec<PgPrivilege>,
    pub grantor: String,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct PgPrivilege {
    pub grant_option: bool,
    pub privilege: PgPrivilegeType,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Deserialize,
    Eq,
    Hash,
    PartialEq,
    Serialize,
    strum::Display,
    strum::EnumIter,
    strum::EnumString,
)]
pub enum PgPrivilegeType {
    #[serde(rename = "r")]
    #[strum(to_string = "r")]
    Select,
    #[serde(rename = "a")]
    #[strum(to_string = "a")]
    Insert,
    #[serde(rename = "w")]
    #[strum(to_string = "w")]
    Update,
    #[serde(rename = "d")]
    #[strum(to_string = "d")]
    Delete,
    #[serde(rename = "D")]
    #[strum(to_string = "D")]
    Truncate,
    #[serde(rename = "x")]
    #[strum(to_string = "x")]
    References,
    #[serde(rename = "t")]
    #[strum(to_string = "t")]
    Trigger,
    #[serde(rename = "C")]
    #[strum(to_string = "C")]
    Create,
    #[serde(rename = "c")]
    #[strum(to_string = "c")]
    Connect,
    #[serde(rename = "T")]
    #[strum(to_string = "T")]
    Temporary,
    #[serde(rename = "X")]
    #[strum(to_string = "X")]
    Execute,
    #[serde(rename = "U")]
    #[strum(to_string = "U")]
    Usage,
    #[serde(rename = "s")]
    #[strum(to_string = "s")]
    Set,
    #[serde(rename = "A")]
    #[strum(to_string = "A")]
    AlterSystem,
    #[serde(rename = "m")]
    #[strum(to_string = "m")]
    Maintain,
}

impl<'a> Decode<'a, Postgres> for PgAclItem {
    fn decode(value: PgValueRef<'a>) -> Result<Self, BoxDynError> {
        let acl_item_str = <&str as Decode<Postgres>>::decode(value)?;
        let (remainder, acl_item) = parse_acl_item::<(_, nom::error::ErrorKind)>(acl_item_str)
            .map_err(|err| err.to_owned())?;
        assert_eq!(remainder, "");
        Ok(acl_item)
    }
}

impl sqlx::Type<Postgres> for PgAclItem {
    fn type_info() -> PgTypeInfo {
        PgTypeInfo::with_name("aclitem")
    }
}

impl PgHasArrayType for PgAclItem {
    fn array_type_info() -> PgTypeInfo {
        PgTypeInfo::array_of("aclitem")
    }
}

fn parse_acl_item<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, PgAclItem, E> {
    let (remainder, grantee) = parse_identifier(input)?;
    let (remainder, _) = char('=').parse(remainder)?;
    let (remainder, privileges) = parse_privileges(remainder)?;
    let (remainder, _) = char('/').parse(remainder)?;
    let (remainder, grantor) = parse_identifier(remainder)?;
    Ok((
        remainder,
        PgAclItem {
            grantee,
            privileges,
            grantor,
        },
    ))
}

fn parse_identifier<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, String, E> {
    alt((
        delimited(char('"'), parse_escaped_identifier, char('"')),
        parse_plain_identifier,
    ))
    .parse(input)
}

/// WARNING: This works correctly only for identifiers read from aclitem
/// strings, as they are quote-escaped whenever the identifier contains
/// characters other than alphanumerics and underscores.
fn parse_plain_identifier<'a, E: ParseError<&'a str>>(
    input: &'a str,
) -> IResult<&'a str, String, E> {
    take_till(|c: char| !c.is_alphanum() && c != '_')
        .parse(input)
        .map(|(remainder, parsed)| (remainder, parsed.to_owned()))
}

fn parse_escaped_identifier<'a, E: ParseError<&'a str>>(
    input: &'a str,
) -> IResult<&'a str, String, E> {
    let (remainder, parsed) = many1(alt((value("\"", tag("\"\"")), is_not("\"")))).parse(input)?;
    Ok((remainder, parsed.join("")))
}

fn parse_privileges<'a, E: ParseError<&'a str>>(
    input: &'a str,
) -> IResult<&'a str, Vec<PgPrivilege>, E> {
    many0(parse_privilege).parse(input)
}

fn parse_privilege<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, PgPrivilege, E> {
    // [`tag`] does not take owned [`String`]s, so we must first store those in
    // a [`Vec`] whose items can be referenced durably throughout this function.
    let branches_owned: Vec<_> = PgPrivilegeType::iter()
        .map(|priv_type| (priv_type, priv_type.to_string()))
        .collect();
    // [`alt`] can take a slice of branches as its `List` argument, but it must
    // be mutable for some reason.
    let mut branches: Vec<_> = branches_owned
        .iter()
        .map(|(branch_value, branch_tag_owned)| value(branch_value, tag(branch_tag_owned.as_str())))
        .collect();
    let (remainder, priv_type) = alt(branches.as_mut_slice()).parse(input)?;
    let (remainder, parsed_grant_option) = opt(char('*')).parse(remainder)?;
    Ok((
        remainder,
        PgPrivilege {
            grant_option: parsed_grant_option.is_some(),
            privilege: *priv_type,
        },
    ))
}
