pub use shopless_types::Country;

#[derive(Debug, PartialEq, Clone, sqlx::FromRow)]
pub struct Address {
    pub id: i64,
    pub recipient: String,
    pub line1: String,
    pub line2: String,
    pub postal_code: String,
    pub city: String,
    pub province_code: Option<String>,
    pub province_name: Option<String>,
    pub country_code: Country,
    pub country_name: String,
    pub phone: Option<String>,
    pub vat: Option<String>,
}

pub async fn fetch<'e>(
    id: i64,
    conn: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Option<Address>, sqlx::Error> {
    sqlx::query_as("SELECT * FROM addresses WHERE id = $1")
        .bind(id)
        .fetch_optional(conn)
        .await
}

#[derive(Debug)]
pub struct NewAddress<'a> {
    pub recipient: &'a str,
    pub line1: &'a str,
    pub line2: &'a str,
    pub postal_code: &'a str,
    pub city: &'a str,
    pub province_code: Option<&'a str>,
    pub province_name: Option<&'a str>,
    pub country_code: Country,
    pub country_name: &'a str,
    pub phone: Option<&'a str>,
}

pub async fn insert<'e>(
    address: &NewAddress<'_>,
    conn: impl sqlx::Executor<'_, Database = sqlx::Postgres>,
) -> Result<Address, sqlx::Error> {
    // deconstruct to not miss new properties in the future
    let NewAddress {
        recipient,
        line1,
        line2,
        postal_code,
        city,
        province_code,
        province_name,
        country_code,
        country_name,
        phone,
    } = address;

    sqlx::query_as(
        "
            INSERT INTO addresses (
                recipient,
                line1,
                line2,
                postal_code,
                city,
                province_code,
                province_name,
                country_code,
                country_name,
                phone
            ) VALUES (
                $1, $2, $3, $4, $5, $6, $7, $8, $9, $10
            )
            RETURNING *
        ",
    )
    .bind(recipient)
    .bind(line1)
    .bind(line2)
    .bind(postal_code)
    .bind(city)
    .bind(province_code)
    .bind(province_name)
    .bind(country_code)
    .bind(country_name)
    .bind(phone)
    .fetch_one(conn)
    .await
}

#[derive(Debug, PartialEq)]
pub struct FirstLastName<'a> {
    pub first_name: &'a str,
    pub last_name: &'a str,
}

impl Address {
    pub fn first_last_name(&self) -> FirstLastName<'_> {
        let fullname = self.recipient.trim();
        if let Some(ix) = fullname.rfind(' ') {
            let (first_name, last_name) = fullname.split_at(ix);
            FirstLastName {
                first_name,
                last_name: last_name.trim(),
            }
        } else {
            FirstLastName {
                first_name: fullname,
                last_name: fullname,
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::{Address, FirstLastName};
    use shopless_types::Country;

    impl<'a> FirstLastName<'a> {
        pub fn new(first_name: &'a str, last_name: &'a str) -> Self {
            FirstLastName {
                first_name,
                last_name,
            }
        }
    }

    impl Address {
        fn with_name(fullname: &str) -> Self {
            Address {
                id: 0,
                recipient: fullname.to_string(),
                line1: String::new(),
                line2: String::new(),
                postal_code: String::new(),
                city: String::new(),
                province_code: None,
                province_name: None,
                country_code: Country::DE,
                country_name: String::new(),
                phone: None,
                vat: None,
            }
        }
    }

    #[test]
    fn first_last_name_split() {
        assert_eq!(
            Address::with_name("Foo Bar").first_last_name(),
            FirstLastName::new("Foo", "Bar")
        );
        assert_eq!(
            Address::with_name("Foo Bar ").first_last_name(),
            FirstLastName::new("Foo", "Bar")
        );
        assert_eq!(
            Address::with_name("Foo A Bar").first_last_name(),
            FirstLastName::new("Foo A", "Bar")
        );
        assert_eq!(
            Address::with_name("Foo A-Bar").first_last_name(),
            FirstLastName::new("Foo", "A-Bar")
        );
        assert_eq!(
            Address::with_name("Foo Bar GmbH - EDV").first_last_name(),
            FirstLastName::new("Foo Bar GmbH -", "EDV")
        );
        assert_eq!(
            Address::with_name("Foobar").first_last_name(),
            FirstLastName::new("Foobar", "Foobar")
        );
    }
}
